From 67c8671852a207dfc5932e255702d4895302cd6f Mon Sep 17 00:00:00 2001 From: wuxu92 Date: Wed, 6 Nov 2024 19:32:36 +0800 Subject: [PATCH 1/3] data factory add support of managed hsm key, update key vault key support both versioned and versionless id --- .../key_vault_or_managed_hsm_key.go | 7 + .../datafactory/data_factory_resource.go | 80 ++++--- .../datafactory/data_factory_resource_test.go | 199 ++++++++++++++++++ website/docs/r/data_factory.html.markdown | 6 +- 4 files changed, 260 insertions(+), 32 deletions(-) diff --git a/internal/customermanagedkeys/key_vault_or_managed_hsm_key.go b/internal/customermanagedkeys/key_vault_or_managed_hsm_key.go index 97ad72327d00..f1b90c8bd01e 100644 --- a/internal/customermanagedkeys/key_vault_or_managed_hsm_key.go +++ b/internal/customermanagedkeys/key_vault_or_managed_hsm_key.go @@ -5,6 +5,7 @@ package customermanagedkeys import ( "fmt" + "strings" "github.com/hashicorp/go-azure-sdk/sdk/environments" "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" @@ -196,3 +197,9 @@ func FlattenKeyVaultOrManagedHSMID(id string, hsmEnv environments.Api) (*KeyVaul return nil, fmt.Errorf("cannot parse given id to key vault key nor managed hsm key: %s", id) } + +func FlattenKeyVaultOrManagedHSMIDByComponents(baseUri, name, version string, hsmEnv environments.Api) (*KeyVaultOrManagedHSMKey, error) { + id := fmt.Sprintf("%s/keys/%s/%s", strings.TrimRight(baseUri, "/"), name, version) + id = strings.TrimRight(id, "/") + return FlattenKeyVaultOrManagedHSMID(id, hsmEnv) +} diff --git a/internal/services/datafactory/data_factory_resource.go b/internal/services/datafactory/data_factory_resource.go index ac2100237564..033fe055fb27 100644 --- a/internal/services/datafactory/data_factory_resource.go +++ b/internal/services/datafactory/data_factory_resource.go @@ -21,10 +21,11 @@ import ( "github.com/hashicorp/go-azure-sdk/resource-manager/purview/2021-07-01/account" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/customermanagedkeys" "github.com/hashicorp/terraform-provider-azurerm/internal/services/datafactory/migration" "github.com/hashicorp/terraform-provider-azurerm/internal/services/datafactory/validate" - keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" + hsmValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/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" @@ -200,16 +201,25 @@ func resourceDataFactory() *pluginsdk.Resource { }, "customer_managed_key_id": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: keyVaultValidate.NestedItemId, + Type: pluginsdk.TypeString, + Optional: true, + ConflictsWith: []string{"customer_managed_hsm_key_id"}, + ValidateFunc: keyVaultValidate.NestedItemId, + RequiredWith: []string{"customer_managed_key_identity_id"}, + }, + + "customer_managed_hsm_key_id": { + Type: pluginsdk.TypeString, + Optional: true, + ConflictsWith: []string{"customer_managed_key_id"}, + ValidateFunc: validation.Any(hsmValidate.ManagedHSMDataPlaneVersionedKeyID, hsmValidate.ManagedHSMDataPlaneVersionlessKeyID), + RequiredWith: []string{"customer_managed_key_identity_id"}, }, "customer_managed_key_identity_id": { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: commonids.ValidateUserAssignedIdentityID, - RequiredWith: []string{"customer_managed_key_id"}, }, "tags": commonschema.Tags(), @@ -227,6 +237,7 @@ func resourceDataFactoryCreateUpdate(d *pluginsdk.ResourceData, meta interface{} client := meta.(*clients.Client).DataFactory.Factories managedVirtualNetworksClient := meta.(*clients.Client).DataFactory.ManagedVirtualNetworks subscriptionId := meta.(*clients.Client).Account.SubscriptionId + envs := meta.(*clients.Client).Account.Environment ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -271,19 +282,30 @@ func resourceDataFactoryCreateUpdate(d *pluginsdk.ResourceData, meta interface{} } } - if keyVaultKeyID, ok := d.GetOk("customer_managed_key_id"); ok { - keyVaultKey, err := keyVaultParse.ParseOptionallyVersionedNestedItemID(keyVaultKeyID.(string)) - if err != nil { - return fmt.Errorf("could not parse Key Vault Key ID: %+v", err) + if cmk, err := customermanagedkeys.ExpandKeyVaultOrManagedHSMKeyWithCustomFieldKey(d, customermanagedkeys.VersionTypeAny, + "customer_managed_key_id", "customer_managed_hsm_key_id", envs.KeyVault, envs.ManagedHSM); err != nil { + return fmt.Errorf("expanding CMK: %+v", err) + } else if cmk != nil { + + enc := &factories.EncryptionConfiguration{ + VaultBaseURL: cmk.BaseUri(), } - payload.Properties.Encryption = &factories.EncryptionConfiguration{ - VaultBaseURL: keyVaultKey.KeyVaultBaseUrl, - KeyName: keyVaultKey.Name, - KeyVersion: &keyVaultKey.Version, - Identity: &factories.CMKIdentityDefinition{ - UserAssignedIdentity: utils.String(d.Get("customer_managed_key_identity_id").(string)), - }, + if cmk.KeyVaultKeyId != nil { + enc.KeyName = cmk.KeyVaultKeyId.Name + enc.KeyVersion = pointer.To(cmk.KeyVaultKeyId.Version) + } else if cmk.ManagedHSMKeyId != nil { + enc.KeyName = cmk.ManagedHSMKeyId.KeyName + enc.KeyVersion = pointer.To(cmk.ManagedHSMKeyId.KeyVersion) + } else if cmk.ManagedHSMKeyVersionlessId != nil { + enc.KeyName = cmk.ManagedHSMKeyVersionlessId.KeyName + } + payload.Properties.Encryption = enc + + if identityStr, ok := d.GetOk("customer_managed_key_identity_id"); ok { + payload.Properties.Encryption.Identity = &factories.CMKIdentityDefinition{ + UserAssignedIdentity: pointer.To(identityStr.(string)), + } } } @@ -370,19 +392,19 @@ func resourceDataFactoryRead(d *pluginsdk.ResourceData, meta interface{}) error } if props := model.Properties; props != nil { - customerManagedKeyId := "" - customerManagedKeyIdentityId := "" if enc := props.Encryption; enc != nil { - if enc.VaultBaseURL != "" && enc.KeyName != "" && enc.KeyVersion != nil { - version := "" - if enc.KeyVersion != nil && *enc.KeyVersion != "" { - version = *enc.KeyVersion - } - keyId, err := keyVaultParse.NewNestedKeyID(enc.VaultBaseURL, enc.KeyName, version) - if err != nil { - return fmt.Errorf("parsing Nested Item ID: %+v", err) + if enc.VaultBaseURL != "" && enc.KeyName != "" { + if cmk, err := customermanagedkeys.FlattenKeyVaultOrManagedHSMIDByComponents( + enc.VaultBaseURL, enc.KeyName, pointer.From(enc.KeyVersion), + meta.(*clients.Client).Account.Environment.ManagedHSM); err != nil { + return fmt.Errorf("flattening CMK: %+v", err) + } else if cmk != nil { + if cmk.KeyVaultKeyId != nil { + d.Set("customer_managed_key_id", cmk.KeyVaultKeyId.ID()) + } else { + d.Set("customer_managed_hsm_key_id", cmk.ManagedHSMKeyID()) + } } - customerManagedKeyId = keyId.ID() } if encIdentity := enc.Identity; encIdentity != nil && encIdentity.UserAssignedIdentity != nil { @@ -390,11 +412,9 @@ func resourceDataFactoryRead(d *pluginsdk.ResourceData, meta interface{}) error if err != nil { return fmt.Errorf("parsing %q: %+v", *encIdentity.UserAssignedIdentity, err) } - customerManagedKeyIdentityId = parsed.ID() + d.Set("customer_managed_key_identity_id", parsed.ID()) } } - d.Set("customer_managed_key_id", customerManagedKeyId) - d.Set("customer_managed_key_identity_id", customerManagedKeyIdentityId) globalParameters, err := flattenDataFactoryGlobalParameters(props.GlobalParameters) if err != nil { diff --git a/internal/services/datafactory/data_factory_resource_test.go b/internal/services/datafactory/data_factory_resource_test.go index aa5f7bf57ec7..32fd4abd7a8a 100644 --- a/internal/services/datafactory/data_factory_resource_test.go +++ b/internal/services/datafactory/data_factory_resource_test.go @@ -6,8 +6,10 @@ package datafactory_test import ( "context" "fmt" + "os" "testing" + "github.com/google/uuid" "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-sdk/resource-manager/datafactory/2018-06-01/factories" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" @@ -240,6 +242,26 @@ func TestAccDataFactory_keyVaultKeyEncryption(t *testing.T) { }) } +func TestAccDataFactory_managedHSMKeyEncryption(t *testing.T) { + if os.Getenv("ARM_TEST_HSM_KEY") == "" { + t.Skip("Skipping as ARM_TEST_HSM_KEY is not specified") + return + } + + data := acceptance.BuildTestData(t, "azurerm_data_factory", "test") + r := DataFactoryResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.managedHSMKeyEncryption(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccDataFactory_globalParameter(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_data_factory", "test") r := DataFactoryResource{} @@ -738,6 +760,183 @@ resource "azurerm_data_factory" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) } +func (DataFactoryResource) managedHSMKeyEncryption(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + } +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-df-%[1]d" + location = "%[2]s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctest%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_key_vault" "test" { + name = "acc%[1]d" + 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 + 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%[1]d" + 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 = true + soft_delete_retention_days = 7 + + security_domain_key_vault_certificate_ids = [for cert in azurerm_key_vault_certificate.cert : cert.id] + security_domain_quorum = 3 +} + +data "azurerm_key_vault_managed_hardware_security_module_role_definition" "crypto-officer" { + name = "515eb02d-2335-4d2d-92f2-b1cbdf9c3778" + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id +} + +data "azurerm_key_vault_managed_hardware_security_module_role_definition" "crypto-user" { + name = "21dbd100-6940-42c2-9190-5d6cb909625b" + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id +} + +data "azurerm_key_vault_managed_hardware_security_module_role_definition" "encrypt-user" { + name = "33413926-3206-4cdd-b39a-83574fe37a17" + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id +} + +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "client1" { + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id + name = "%[3]s" + scope = "/keys" + role_definition_id = data.azurerm_key_vault_managed_hardware_security_module_role_definition.crypto-officer.resource_manager_id + principal_id = data.azurerm_client_config.current.object_id +} + +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "client2" { + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id + name = "%[4]s" + scope = "/keys" + role_definition_id = data.azurerm_key_vault_managed_hardware_security_module_role_definition.crypto-user.resource_manager_id + principal_id = data.azurerm_client_config.current.object_id +} + +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "radf" { + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id + name = "%[5]s" + scope = "/keys" + role_definition_id = data.azurerm_key_vault_managed_hardware_security_module_role_definition.encrypt-user.resource_manager_id + principal_id = azurerm_user_assigned_identity.test.principal_id + + depends_on = [azurerm_key_vault_managed_hardware_security_module_key.test] +} + +resource "azurerm_key_vault_managed_hardware_security_module_key" "test" { + name = "acctestHSMK-%[1]d" + 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.client1, + azurerm_key_vault_managed_hardware_security_module_role_assignment.client2 + ] +} + +resource "azurerm_data_factory" "test" { + name = "acctest%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + identity { + type = "UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id + ] + } + + customer_managed_hsm_key_id = azurerm_key_vault_managed_hardware_security_module_key.test.id + customer_managed_key_identity_id = azurerm_user_assigned_identity.test.id + + depends_on = [azurerm_key_vault_managed_hardware_security_module_role_assignment.radf] +} +`, data.RandomInteger, data.Locations.Primary, uuid.NewString(), uuid.NewString(), uuid.NewString()) +} + func (DataFactoryResource) globalParameter(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/r/data_factory.html.markdown b/website/docs/r/data_factory.html.markdown index b51008c6f57a..17b533ddee10 100644 --- a/website/docs/r/data_factory.html.markdown +++ b/website/docs/r/data_factory.html.markdown @@ -47,9 +47,11 @@ The following arguments are supported: * `public_network_enabled` - (Optional) Is the Data Factory visible to the public network? Defaults to `true`. -* `customer_managed_key_id` - (Optional) Specifies the Azure Key Vault Key ID to be used as the Customer Managed Key (CMK) for double encryption. Required with user assigned identity. +* `customer_managed_key_id` - (Optional) Specifies the Azure Key Vault Key ID to be used as the Customer Managed Key (CMK) for double encryption. Required with `customer_managed_key_identity_id`. -* `customer_managed_key_identity_id` - (Optional) Specifies the ID of the user assigned identity associated with the Customer Managed Key. Must be supplied if `customer_managed_key_id` is set. +* `customer_managed_hsm_key_id` - (Optional) Specifies the Managed HSM Key ID to be used as the Customer Managed Key (CMK) for double encryption. Both versioned or versionless ID are supported. Required with `customer_managed_key_identity_id`. + +* `customer_managed_key_identity_id` - (Optional) Specifies the ID of the user assigned identity associated with the Customer Managed Key. Must be supplied if `customer_managed_key_id` or `customer_managed_hsm_key_id` is set. * `purview_id` - (Optional) Specifies the ID of the purview account resource associated with the Data Factory. From 71ea21ad9286efe4dbcadbbbd20fe42add4145e7 Mon Sep 17 00:00:00 2001 From: wuxu92 Date: Wed, 6 Nov 2024 19:48:03 +0800 Subject: [PATCH 2/3] code format --- internal/services/datafactory/data_factory_resource.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/services/datafactory/data_factory_resource.go b/internal/services/datafactory/data_factory_resource.go index 033fe055fb27..15d4df4c73ee 100644 --- a/internal/services/datafactory/data_factory_resource.go +++ b/internal/services/datafactory/data_factory_resource.go @@ -291,13 +291,14 @@ func resourceDataFactoryCreateUpdate(d *pluginsdk.ResourceData, meta interface{} VaultBaseURL: cmk.BaseUri(), } - if cmk.KeyVaultKeyId != nil { + switch { + case cmk.KeyVaultKeyId != nil: enc.KeyName = cmk.KeyVaultKeyId.Name enc.KeyVersion = pointer.To(cmk.KeyVaultKeyId.Version) - } else if cmk.ManagedHSMKeyId != nil { + case cmk.ManagedHSMKeyId != nil: enc.KeyName = cmk.ManagedHSMKeyId.KeyName enc.KeyVersion = pointer.To(cmk.ManagedHSMKeyId.KeyVersion) - } else if cmk.ManagedHSMKeyVersionlessId != nil { + case cmk.ManagedHSMKeyVersionlessId != nil: enc.KeyName = cmk.ManagedHSMKeyVersionlessId.KeyName } payload.Properties.Encryption = enc From 614bd1292937960c3ef95a9e05f43e6158003d44 Mon Sep 17 00:00:00 2001 From: wuxu92 Date: Fri, 8 Nov 2024 14:24:09 +0800 Subject: [PATCH 3/3] remove required for key field --- internal/services/datafactory/data_factory_resource.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/services/datafactory/data_factory_resource.go b/internal/services/datafactory/data_factory_resource.go index 15d4df4c73ee..f7dfc3f2462c 100644 --- a/internal/services/datafactory/data_factory_resource.go +++ b/internal/services/datafactory/data_factory_resource.go @@ -205,7 +205,6 @@ func resourceDataFactory() *pluginsdk.Resource { Optional: true, ConflictsWith: []string{"customer_managed_hsm_key_id"}, ValidateFunc: keyVaultValidate.NestedItemId, - RequiredWith: []string{"customer_managed_key_identity_id"}, }, "customer_managed_hsm_key_id": { @@ -213,7 +212,6 @@ func resourceDataFactory() *pluginsdk.Resource { Optional: true, ConflictsWith: []string{"customer_managed_key_id"}, ValidateFunc: validation.Any(hsmValidate.ManagedHSMDataPlaneVersionedKeyID, hsmValidate.ManagedHSMDataPlaneVersionlessKeyID), - RequiredWith: []string{"customer_managed_key_identity_id"}, }, "customer_managed_key_identity_id": {