From b16a8098cfbb29e291def23bb549c2021d3a5d15 Mon Sep 17 00:00:00 2001 From: Catriona Date: Fri, 10 Jan 2025 17:44:19 +0000 Subject: [PATCH] support for managed_hsm_key_id --- ...ce_transparent_data_encryption_resource.go | 81 ++++++- ...ansparent_data_encryption_resource_test.go | 210 +++++++++++++++++- ..._transparent_data_encryption.html.markdown | 2 + 3 files changed, 279 insertions(+), 14 deletions(-) diff --git a/internal/services/mssqlmanagedinstance/mssql_managed_instance_transparent_data_encryption_resource.go b/internal/services/mssqlmanagedinstance/mssql_managed_instance_transparent_data_encryption_resource.go index eeb19b5c4bbd..195b244e7f7a 100644 --- a/internal/services/mssqlmanagedinstance/mssql_managed_instance_transparent_data_encryption_resource.go +++ b/internal/services/mssqlmanagedinstance/mssql_managed_instance_transparent_data_encryption_resource.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" "github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-08-01-preview/managedinstanceencryptionprotectors" @@ -17,6 +18,9 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/clients" keyVaultParser "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" + managedHsmHelpers "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/helpers" + mhsmParser "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/parse" + managedhsmValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/services/mssqlmanagedinstance/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/services/mssqlmanagedinstance/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -52,9 +56,17 @@ func resourceMsSqlManagedInstanceTransparentDataEncryption() *pluginsdk.Resource ValidateFunc: validate.ManagedInstanceID, }, "key_vault_key_id": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: keyVaultValidate.NestedItemId, + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: keyVaultValidate.NestedItemId, + ConflictsWith: []string{"managed_hsm_key_id"}, + }, + + "managed_hsm_key_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: managedhsmValidate.ManagedHSMDataPlaneVersionedKeyID, + ConflictsWith: []string{"key_vault_key_id"}, }, "auto_rotation_enabled": { Type: pluginsdk.TypeBool, @@ -89,10 +101,9 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionCreateUpdate(d *plugin managedInstanceKeyName := "" managedInstanceKeyType := managedinstancekeys.ServerKeyTypeServiceManaged - keyVaultKeyId := strings.TrimSpace(d.Get("key_vault_key_id").(string)) - // If it has content, then we assume it's a key vault key id - if keyVaultKeyId != "" { + if v, ok := d.GetOk("key_vault_key_id"); ok { + keyVaultKeyId := strings.TrimSpace(v.(string)) // Update the server key type to AKV managedInstanceKeyType = managedinstancekeys.ServerKeyTypeAzureKeyVault @@ -100,7 +111,7 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionCreateUpdate(d *plugin managedInstanceKeyProperties := managedinstancekeys.ManagedInstanceKeyProperties{ ServerKeyType: managedInstanceKeyType, Uri: &keyVaultKeyId, - AutoRotationEnabled: utils.Bool(d.Get("auto_rotation_enabled").(bool)), + AutoRotationEnabled: pointer.To(d.Get("auto_rotation_enabled").(bool)), } managedInstanceKey.Properties = &managedInstanceKeyProperties @@ -131,6 +142,38 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionCreateUpdate(d *plugin } } + if v, ok := d.GetOk("managed_hsm_key_id"); ok { + mhsmKeyId := strings.TrimSpace(v.(string)) + // Update the server key type to AKV + managedInstanceKeyType = managedinstancekeys.ServerKeyTypeAzureKeyVault + + // Set the SQL Server Key properties z + serverKeyProperties := managedinstancekeys.ManagedInstanceKeyProperties{ + ServerKeyType: managedInstanceKeyType, + Uri: &mhsmKeyId, + AutoRotationEnabled: pointer.To(d.Get("auto_rotation_enabled").(bool)), + } + managedInstanceKey.Properties = &serverKeyProperties + + // Make sure it's a key, if not, throw an error + keyId, err := mhsmParser.ManagedHSMDataPlaneVersionedKeyID(mhsmKeyId, nil) + if err != nil { + return fmt.Errorf("failed to parse '%s' as HSM key ID", mhsmKeyId) + } + + // Extract the vault name from the keyvault base url + idURL, err := url.ParseRequestURI(keyId.BaseUri()) + if err != nil { + return fmt.Errorf("unable to parse key vault hostname: %s", keyId.BaseUri()) + } + + hostParts := strings.Split(idURL.Host, ".") + vaultName := hostParts[0] + + // Create the key path for the Encryption Protector. Format is: {vaultname}_{key}_{key_version} + managedInstanceKeyName = fmt.Sprintf("%s_%s_%s", vaultName, keyId.KeyName, keyId.KeyVersion) + } + keyType := managedinstanceencryptionprotectors.ServerKeyTypeServiceManaged if managedInstanceKeyType == managedinstancekeys.ServerKeyTypeAzureKeyVault { keyType = managedinstanceencryptionprotectors.ServerKeyTypeAzureKeyVault @@ -171,6 +214,7 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionCreateUpdate(d *plugin func resourceMsSqlManagedInstanceTransparentDataEncryptionRead(d *pluginsdk.ResourceData, meta interface{}) error { encryptionProtectorClient := meta.(*clients.Client).MSSQLManagedInstance.ManagedInstanceEncryptionProtectorClient + env := meta.(*clients.Client).Account.Environment ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() @@ -196,14 +240,14 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionRead(d *pluginsdk.Reso if resp.Model != nil && resp.Model.Properties != nil { log.Printf("[INFO] Encryption protector key type is %s", resp.Model.Properties.ServerKeyType) - keyVaultKeyId := "" + keyId := "" autoRotationEnabled := false // Only set the key type if it's an AKV key. For service managed, we can omit the setting the key_vault_key_id if resp.Model.Properties.ServerKeyType == managedinstanceencryptionprotectors.ServerKeyTypeAzureKeyVault { if resp.Model.Properties.Uri != nil { log.Printf("[INFO] Setting Key Vault URI to %s", *resp.Model.Properties.Uri) - keyVaultKeyId = *resp.Model.Properties.Uri + keyId = *resp.Model.Properties.Uri } // autoRotation is only for AKV keys @@ -212,6 +256,25 @@ func resourceMsSqlManagedInstanceTransparentDataEncryptionRead(d *pluginsdk.Reso } } + hsmKey := "" + keyVaultKeyId := "" + if keyId != "" { + isHSMURI, err, _, _ := managedHsmHelpers.IsManagedHSMURI(env, keyId) + if err != nil { + return err + } + + if isHSMURI { + hsmKey = keyId + } else { + keyVaultKeyId = keyId + } + } + + if err := d.Set("managed_hsm_key_id", hsmKey); err != nil { + return fmt.Errorf("setting `managed_hsm_key_id`: %+v", err) + } + if err := d.Set("key_vault_key_id", keyVaultKeyId); err != nil { return fmt.Errorf("setting `key_vault_key_id`: %+v", err) } diff --git a/internal/services/mssqlmanagedinstance/mssql_managed_instance_transparent_data_encryption_resource_test.go b/internal/services/mssqlmanagedinstance/mssql_managed_instance_transparent_data_encryption_resource_test.go index bb2618512f21..dd63b591a4a6 100644 --- a/internal/services/mssqlmanagedinstance/mssql_managed_instance_transparent_data_encryption_resource_test.go +++ b/internal/services/mssqlmanagedinstance/mssql_managed_instance_transparent_data_encryption_resource_test.go @@ -82,6 +82,21 @@ func TestAccMsSqlManagedInstanceTransparentDataEncryption_systemManaged(t *testi }) } +func TestAccMsSqlManagedInstanceTransparentDataEncryption_managedHSM(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_mssql_managed_instance_transparent_data_encryption", "test") + r := MsSqlManagedInstanceTransparentDataEncryptionResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.managedHSM(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccMsSqlManagedInstanceTransparentDataEncryption_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_mssql_managed_instance_transparent_data_encryption", "test") r := MsSqlManagedInstanceTransparentDataEncryptionResource{} @@ -372,10 +387,6 @@ resource "azurerm_mssql_managed_instance" "test" { database = "test" } } - - - - `, db.template(data, data.Locations.Primary), data.RandomInteger) } @@ -440,13 +451,202 @@ resource "azurerm_mssql_managed_instance" "test" { database = "test" } } +`, db.template(data, data.Locations.Primary), data.RandomInteger) +} +func (r MsSqlManagedInstanceTransparentDataEncryptionResource) managedHSM(data acceptance.TestData) string { + db := MsSqlManagedInstanceResource{} + return fmt.Sprintf(` +%s +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + /* Due to the creation of unmanaged Microsoft.Network/networkIntentPolicies in this service, + prevent_deletion_if_contains_resources has been added here to allow the test resources to be + deleted until this can be properly investigated + */ + prevent_deletion_if_contains_resources = false + } + } +} +data "azurerm_client_config" "current" {} +resource "azurerm_key_vault" "test" { + name = "acctest%[2]s" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + soft_delete_retention_days = 7 + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + key_permissions = [ + "Create", + "Delete", + "Get", + "Purge", + "Recover", + "Update", + "GetRotationPolicy", + ] + secret_permissions = [ + "Delete", + "Get", + "Set", + ] + certificate_permissions = [ + "Create", + "Delete", + "DeleteIssuers", + "Get", + "Purge", + "Update" + ] + } + tags = { + environment = "Production" + } +} +resource "azurerm_key_vault_certificate" "cert" { + count = 3 + name = "acchsmcert${count.index}" + key_vault_id = azurerm_key_vault.test.id + certificate_policy { + issuer_parameters { + name = "Self" + } + key_properties { + exportable = true + key_size = 2048 + key_type = "RSA" + reuse_key = true + } + lifetime_action { + action { + action_type = "AutoRenew" + } + trigger { + days_before_expiry = 30 + } + } + secret_properties { + content_type = "application/x-pkcs12" + } + x509_certificate_properties { + extended_key_usage = [] + key_usage = [ + "cRLSign", + "dataEncipherment", + "digitalSignature", + "keyAgreement", + "keyCertSign", + "keyEncipherment", + ] + subject = "CN=hello-world" + validity_in_months = 12 + } + } +} + +resource "azurerm_key_vault_managed_hardware_security_module" "test" { + name = "kvHsm%[2]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku_name = "Standard_B1" + tenant_id = data.azurerm_client_config.current.tenant_id + admin_object_ids = [data.azurerm_client_config.current.object_id] + purge_protection_enabled = false + + security_domain_key_vault_certificate_ids = [for cert in azurerm_key_vault_certificate.cert : cert.id] + security_domain_quorum = 3 +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctestmi%[2]s" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "test" { + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id + name = "1e243909-064c-6ac3-84e9-1c8bf8d6ad22" + scope = "/keys" + role_definition_id = "/Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/21dbd100-6940-42c2-9190-5d6cb909625b" + principal_id = data.azurerm_client_config.current.object_id +} +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "test1" { + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id + name = "1e243909-064c-6ac3-84e9-1c8bf8d6ad23" + scope = "/keys" + role_definition_id = "/Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/515eb02d-2335-4d2d-92f2-b1cbdf9c3778" + principal_id = data.azurerm_client_config.current.object_id +} -`, db.template(data, data.Locations.Primary), data.RandomInteger) +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "user" { + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id + name = "1e243909-064c-6ac3-84e9-1c8bf8d6ad20" + scope = "/keys" + role_definition_id = "/Microsoft.KeyVault/providers/Microsoft.Authorization/roleDefinitions/21dbd100-6940-42c2-9190-5d6cb909625b" + principal_id = azurerm_user_assigned_identity.test.principal_id +} + +resource "azurerm_key_vault_managed_hardware_security_module_key" "test" { + name = "acctestHSMK-%[2]s" + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id + key_type = "RSA-HSM" + key_size = 2048 + key_opts = ["unwrapKey", "wrapKey"] + + depends_on = [ + azurerm_key_vault_managed_hardware_security_module_role_assignment.test, + azurerm_key_vault_managed_hardware_security_module_role_assignment.test1 + ] +} + +resource "azurerm_mssql_managed_instance" "test" { + name = "acctestsqlserver%[3]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + + license_type = "BasePrice" + sku_name = "GP_Gen5" + storage_size_in_gb = 32 + subnet_id = azurerm_subnet.test.id + vcores = 4 + + administrator_login = "missadministrator" + administrator_login_password = "NCC-1701-D" + + depends_on = [ + azurerm_subnet_network_security_group_association.test, + azurerm_subnet_route_table_association.test, + ] + + identity { + type = "UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id + ] + } + + tags = { + environment = "staging" + database = "test" + } +} + +resource "azurerm_mssql_managed_instance_transparent_data_encryption" "test" { + managed_instance_id = azurerm_mssql_managed_instance.test.id + managed_hsm_key_id = azurerm_key_vault_managed_hardware_security_module_key.test.versioned_id +} +`, db.template(data, data.Locations.Primary), data.RandomStringOfLength(5), data.RandomInteger) } diff --git a/website/docs/r/mssql_managed_instance_transparent_data_encryption.html.markdown b/website/docs/r/mssql_managed_instance_transparent_data_encryption.html.markdown index 2e6803e8b40e..3beb2b207c7c 100644 --- a/website/docs/r/mssql_managed_instance_transparent_data_encryption.html.markdown +++ b/website/docs/r/mssql_managed_instance_transparent_data_encryption.html.markdown @@ -187,6 +187,8 @@ The following arguments are supported: * `key_vault_key_id` - (Optional) To use customer managed keys from Azure Key Vault, provide the AKV Key ID. To use service managed keys, omit this field. +* `managed_hsm_key_id` - (Optional) To use customer managed keys from a managed HSM, provide the Managed HSM Key ID. To use service managed keys, omit this field. + ~> **NOTE:** In order to use customer managed keys, the identity of the MSSQL Managed Instance must have the following permissions on the key vault: 'get', 'wrapKey' and 'unwrapKey' ~> **NOTE:** If `managed_instance_id` denotes a secondary instance deployed for disaster recovery purposes, then the `key_vault_key_id` should be the same key used for the primary instance's transparent data encryption. Both primary and secondary instances should be encrypted with same key material.