From 02b4634974ebee5ccd0d478292669b70c17aafb4 Mon Sep 17 00:00:00 2001 From: Matthew Frahry Date: Thu, 16 May 2024 11:38:46 -0700 Subject: [PATCH] New Resource: `azurerm_key_vault_managed_hardware_security_module_key` (#25935) --- internal/features/defaults.go | 6 +- internal/features/user_flags.go | 2 + internal/provider/features.go | 20 + internal/provider/features_test.go | 92 ++-- internal/services/managedhsm/client/client.go | 8 +- internal/services/managedhsm/internal.go | 131 +++++ ...d_hardware_security_module_key_resource.go | 500 ++++++++++++++++++ ...dware_security_module_key_resource_test.go | 344 ++++++++++++ ...naged_hardware_security_module_resource.go | 14 +- ..._hardware_security_module_resource_test.go | 6 + .../managed_hsm_data_plane_versioned_key.go | 11 + .../managed_hsm_data_plane_versionless_key.go | 10 + internal/services/managedhsm/registration.go | 1 + .../storage/storage_account_resource_test.go | 191 +++++++ .../docs/guides/features-block.html.markdown | 6 +- ...hardware_security_module_key.html.markdown | 117 ++++ 16 files changed, 1410 insertions(+), 49 deletions(-) create mode 100644 internal/services/managedhsm/internal.go create mode 100644 internal/services/managedhsm/key_vault_managed_hardware_security_module_key_resource.go create mode 100644 internal/services/managedhsm/key_vault_managed_hardware_security_module_key_resource_test.go create mode 100644 website/docs/r/key_vault_managed_hardware_security_module_key.html.markdown diff --git a/internal/features/defaults.go b/internal/features/defaults.go index 8e8e46862181..db16d247735b 100644 --- a/internal/features/defaults.go +++ b/internal/features/defaults.go @@ -25,11 +25,15 @@ func Default() UserFeatures { PurgeSoftDeletedKeysOnDestroy: true, PurgeSoftDeletedCertsOnDestroy: true, PurgeSoftDeletedSecretsOnDestroy: true, - PurgeSoftDeletedHSMsOnDestroy: true, RecoverSoftDeletedKeyVaults: true, RecoverSoftDeletedKeys: true, RecoverSoftDeletedCerts: true, RecoverSoftDeletedSecrets: true, + + // todo 4.0 move all HSM flags into their own features HSMFeatures block + PurgeSoftDeletedHSMsOnDestroy: true, + PurgeSoftDeletedHSMKeysOnDestroy: true, + RecoverSoftDeletedHSMKeys: true, }, LogAnalyticsWorkspace: LogAnalyticsWorkspaceFeatures{ PermanentlyDeleteOnDestroy: true, diff --git a/internal/features/user_flags.go b/internal/features/user_flags.go index 52526e07fb04..257bfea93b5b 100644 --- a/internal/features/user_flags.go +++ b/internal/features/user_flags.go @@ -45,10 +45,12 @@ type KeyVaultFeatures struct { PurgeSoftDeletedCertsOnDestroy bool PurgeSoftDeletedSecretsOnDestroy bool PurgeSoftDeletedHSMsOnDestroy bool + PurgeSoftDeletedHSMKeysOnDestroy bool RecoverSoftDeletedKeyVaults bool RecoverSoftDeletedKeys bool RecoverSoftDeletedCerts bool RecoverSoftDeletedSecrets bool + RecoverSoftDeletedHSMKeys bool } type TemplateDeploymentFeatures struct { diff --git a/internal/provider/features.go b/internal/provider/features.go index ac7f6da7d6ca..1312332ecf29 100644 --- a/internal/provider/features.go +++ b/internal/provider/features.go @@ -129,6 +129,13 @@ func schemaFeatures(supportLegacyTestSuite bool) *pluginsdk.Schema { Default: true, }, + "purge_soft_deleted_hardware_security_module_keys_on_destroy": { + Description: "When enabled soft-deleted `azurerm_key_vault_managed_hardware_security_module_key` resources will be permanently deleted (e.g purged), when destroyed", + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + "recover_soft_deleted_certificates": { Description: "When enabled soft-deleted `azurerm_key_vault_certificate` resources will be restored, instead of creating new ones", Type: pluginsdk.TypeBool, @@ -156,6 +163,13 @@ func schemaFeatures(supportLegacyTestSuite bool) *pluginsdk.Schema { Optional: true, Default: true, }, + + "recover_soft_deleted_hardware_security_module_keys": { + Description: "When enabled soft-deleted `azurerm_key_vault_managed_hardware_security_module_key` resources will be restored, instead of creating new ones", + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, }, }, }, @@ -453,6 +467,9 @@ func expandFeatures(input []interface{}) features.UserFeatures { if v, ok := keyVaultRaw["purge_soft_deleted_hardware_security_modules_on_destroy"]; ok { featuresMap.KeyVault.PurgeSoftDeletedHSMsOnDestroy = v.(bool) } + if v, ok := keyVaultRaw["purge_soft_deleted_hardware_security_module_keys_on_destroy"]; ok { + featuresMap.KeyVault.PurgeSoftDeletedHSMKeysOnDestroy = v.(bool) + } if v, ok := keyVaultRaw["recover_soft_deleted_certificates"]; ok { featuresMap.KeyVault.RecoverSoftDeletedCerts = v.(bool) } @@ -465,6 +482,9 @@ func expandFeatures(input []interface{}) features.UserFeatures { if v, ok := keyVaultRaw["recover_soft_deleted_secrets"]; ok { featuresMap.KeyVault.RecoverSoftDeletedSecrets = v.(bool) } + if v, ok := keyVaultRaw["recover_soft_deleted_hardware_security_module_keys"]; ok { + featuresMap.KeyVault.RecoverSoftDeletedHSMKeys = v.(bool) + } } } diff --git a/internal/provider/features_test.go b/internal/provider/features_test.go index 416595acf669..8ad758fad0bd 100644 --- a/internal/provider/features_test.go +++ b/internal/provider/features_test.go @@ -41,10 +41,12 @@ func TestExpandFeatures(t *testing.T) { PurgeSoftDeletedSecretsOnDestroy: true, PurgeSoftDeleteOnDestroy: true, PurgeSoftDeletedHSMsOnDestroy: true, + PurgeSoftDeletedHSMKeysOnDestroy: true, RecoverSoftDeletedCerts: true, RecoverSoftDeletedKeys: true, RecoverSoftDeletedKeyVaults: true, RecoverSoftDeletedSecrets: true, + RecoverSoftDeletedHSMKeys: true, }, LogAnalyticsWorkspace: features.LogAnalyticsWorkspaceFeatures{ PermanentlyDeleteOnDestroy: true, @@ -115,15 +117,17 @@ func TestExpandFeatures(t *testing.T) { }, "key_vault": []interface{}{ map[string]interface{}{ - "purge_soft_deleted_certificates_on_destroy": true, - "purge_soft_deleted_keys_on_destroy": true, - "purge_soft_deleted_secrets_on_destroy": true, - "purge_soft_deleted_hardware_security_modules_on_destroy": true, - "purge_soft_delete_on_destroy": true, - "recover_soft_deleted_certificates": true, - "recover_soft_deleted_keys": true, - "recover_soft_deleted_key_vaults": true, - "recover_soft_deleted_secrets": true, + "purge_soft_deleted_certificates_on_destroy": true, + "purge_soft_deleted_keys_on_destroy": true, + "purge_soft_deleted_secrets_on_destroy": true, + "purge_soft_deleted_hardware_security_modules_on_destroy": true, + "purge_soft_deleted_hardware_security_module_keys_on_destroy": true, + "purge_soft_delete_on_destroy": true, + "recover_soft_deleted_certificates": true, + "recover_soft_deleted_keys": true, + "recover_soft_deleted_key_vaults": true, + "recover_soft_deleted_secrets": true, + "recover_soft_deleted_hardware_security_module_keys": true, }, }, "log_analytics_workspace": []interface{}{ @@ -210,10 +214,12 @@ func TestExpandFeatures(t *testing.T) { PurgeSoftDeletedSecretsOnDestroy: true, PurgeSoftDeleteOnDestroy: true, PurgeSoftDeletedHSMsOnDestroy: true, + PurgeSoftDeletedHSMKeysOnDestroy: true, RecoverSoftDeletedCerts: true, RecoverSoftDeletedKeys: true, RecoverSoftDeletedKeyVaults: true, RecoverSoftDeletedSecrets: true, + RecoverSoftDeletedHSMKeys: true, }, LogAnalyticsWorkspace: features.LogAnalyticsWorkspaceFeatures{ PermanentlyDeleteOnDestroy: true, @@ -284,15 +290,17 @@ func TestExpandFeatures(t *testing.T) { }, "key_vault": []interface{}{ map[string]interface{}{ - "purge_soft_deleted_certificates_on_destroy": false, - "purge_soft_deleted_keys_on_destroy": false, - "purge_soft_deleted_secrets_on_destroy": false, - "purge_soft_deleted_hardware_security_modules_on_destroy": false, - "purge_soft_delete_on_destroy": false, - "recover_soft_deleted_certificates": false, - "recover_soft_deleted_keys": false, - "recover_soft_deleted_key_vaults": false, - "recover_soft_deleted_secrets": false, + "purge_soft_deleted_certificates_on_destroy": false, + "purge_soft_deleted_keys_on_destroy": false, + "purge_soft_deleted_secrets_on_destroy": false, + "purge_soft_deleted_hardware_security_modules_on_destroy": false, + "purge_soft_deleted_hardware_security_module_keys_on_destroy": false, + "purge_soft_delete_on_destroy": false, + "recover_soft_deleted_certificates": false, + "recover_soft_deleted_keys": false, + "recover_soft_deleted_key_vaults": false, + "recover_soft_deleted_secrets": false, + "recover_soft_deleted_hardware_security_module_keys": false, }, }, "log_analytics_workspace": []interface{}{ @@ -378,11 +386,13 @@ func TestExpandFeatures(t *testing.T) { PurgeSoftDeletedKeysOnDestroy: false, PurgeSoftDeletedSecretsOnDestroy: false, PurgeSoftDeletedHSMsOnDestroy: false, + PurgeSoftDeletedHSMKeysOnDestroy: false, PurgeSoftDeleteOnDestroy: false, RecoverSoftDeletedCerts: false, RecoverSoftDeletedKeys: false, RecoverSoftDeletedKeyVaults: false, RecoverSoftDeletedSecrets: false, + RecoverSoftDeletedHSMKeys: false, }, LogAnalyticsWorkspace: features.LogAnalyticsWorkspaceFeatures{ PermanentlyDeleteOnDestroy: false, @@ -727,10 +737,12 @@ func TestExpandFeaturesKeyVault(t *testing.T) { PurgeSoftDeletedSecretsOnDestroy: true, PurgeSoftDeleteOnDestroy: true, PurgeSoftDeletedHSMsOnDestroy: true, + PurgeSoftDeletedHSMKeysOnDestroy: true, RecoverSoftDeletedCerts: true, RecoverSoftDeletedKeys: true, RecoverSoftDeletedKeyVaults: true, RecoverSoftDeletedSecrets: true, + RecoverSoftDeletedHSMKeys: true, }, }, }, @@ -740,15 +752,17 @@ func TestExpandFeaturesKeyVault(t *testing.T) { map[string]interface{}{ "key_vault": []interface{}{ map[string]interface{}{ - "purge_soft_deleted_certificates_on_destroy": true, - "purge_soft_deleted_keys_on_destroy": true, - "purge_soft_deleted_secrets_on_destroy": true, - "purge_soft_deleted_hardware_security_modules_on_destroy": true, - "purge_soft_delete_on_destroy": true, - "recover_soft_deleted_certificates": true, - "recover_soft_deleted_keys": true, - "recover_soft_deleted_key_vaults": true, - "recover_soft_deleted_secrets": true, + "purge_soft_deleted_certificates_on_destroy": true, + "purge_soft_deleted_keys_on_destroy": true, + "purge_soft_deleted_secrets_on_destroy": true, + "purge_soft_deleted_hardware_security_modules_on_destroy": true, + "purge_soft_deleted_hardware_security_module_keys_on_destroy": true, + "purge_soft_delete_on_destroy": true, + "recover_soft_deleted_certificates": true, + "recover_soft_deleted_keys": true, + "recover_soft_deleted_key_vaults": true, + "recover_soft_deleted_secrets": true, + "recover_soft_deleted_hardware_security_module_keys": true, }, }, }, @@ -759,11 +773,13 @@ func TestExpandFeaturesKeyVault(t *testing.T) { PurgeSoftDeletedKeysOnDestroy: true, PurgeSoftDeletedSecretsOnDestroy: true, PurgeSoftDeletedHSMsOnDestroy: true, + PurgeSoftDeletedHSMKeysOnDestroy: true, PurgeSoftDeleteOnDestroy: true, RecoverSoftDeletedCerts: true, RecoverSoftDeletedKeys: true, RecoverSoftDeletedKeyVaults: true, RecoverSoftDeletedSecrets: true, + RecoverSoftDeletedHSMKeys: true, }, }, }, @@ -773,15 +789,17 @@ func TestExpandFeaturesKeyVault(t *testing.T) { map[string]interface{}{ "key_vault": []interface{}{ map[string]interface{}{ - "purge_soft_deleted_certificates_on_destroy": false, - "purge_soft_deleted_keys_on_destroy": false, - "purge_soft_deleted_secrets_on_destroy": false, - "purge_soft_deleted_hardware_security_modules_on_destroy": false, - "purge_soft_delete_on_destroy": false, - "recover_soft_deleted_certificates": false, - "recover_soft_deleted_keys": false, - "recover_soft_deleted_key_vaults": false, - "recover_soft_deleted_secrets": false, + "purge_soft_deleted_certificates_on_destroy": false, + "purge_soft_deleted_keys_on_destroy": false, + "purge_soft_deleted_secrets_on_destroy": false, + "purge_soft_deleted_hardware_security_modules_on_destroy": false, + "purge_soft_deleted_hardware_security_module_keys_on_destroy": false, + "purge_soft_delete_on_destroy": false, + "recover_soft_deleted_certificates": false, + "recover_soft_deleted_keys": false, + "recover_soft_deleted_key_vaults": false, + "recover_soft_deleted_secrets": false, + "recover_soft_deleted_hardware_security_module_keys": false, }, }, }, @@ -793,10 +811,12 @@ func TestExpandFeaturesKeyVault(t *testing.T) { PurgeSoftDeletedSecretsOnDestroy: false, PurgeSoftDeleteOnDestroy: false, PurgeSoftDeletedHSMsOnDestroy: false, + PurgeSoftDeletedHSMKeysOnDestroy: false, RecoverSoftDeletedCerts: false, RecoverSoftDeletedKeyVaults: false, RecoverSoftDeletedKeys: false, RecoverSoftDeletedSecrets: false, + RecoverSoftDeletedHSMKeys: false, }, }, }, diff --git a/internal/services/managedhsm/client/client.go b/internal/services/managedhsm/client/client.go index 0da9356e36c2..a1aa73f48964 100644 --- a/internal/services/managedhsm/client/client.go +++ b/internal/services/managedhsm/client/client.go @@ -23,7 +23,7 @@ type Client struct { ManagedHsmClient *managedhsms.ManagedHsmsClient // Data Plane - DataPlaneClient *dataplane.BaseClient + DataPlaneKeysClient *dataplane.BaseClient DataPlaneRoleAssignmentsClient *dataplane.RoleAssignmentsClient DataPlaneRoleDefinitionsClient *dataplane.RoleDefinitionsClient DataPlaneSecurityDomainsClient *dataplane.HSMSecurityDomainClient @@ -36,8 +36,8 @@ func NewClient(o *common.ClientOptions) (*Client, error) { } o.Configure(managedHsmClient.Client, o.Authorizers.ResourceManager) - managementClient := dataplane.New() - o.ConfigureClient(&managementClient.Client, o.KeyVaultAuthorizer) + managementKeysClient := dataplane.New() + o.ConfigureClient(&managementKeysClient.Client, o.ManagedHSMAuthorizer) securityDomainClient := dataplane.NewHSMSecurityDomainClient() o.ConfigureClient(&securityDomainClient.Client, o.ManagedHSMAuthorizer) @@ -53,7 +53,7 @@ func NewClient(o *common.ClientOptions) (*Client, error) { ManagedHsmClient: managedHsmClient, // Data Plane - DataPlaneClient: &managementClient, + DataPlaneKeysClient: &managementKeysClient, DataPlaneSecurityDomainsClient: &securityDomainClient, DataPlaneRoleDefinitionsClient: &roleDefinitionsClient, DataPlaneRoleAssignmentsClient: &roleAssignmentsClient, diff --git a/internal/services/managedhsm/internal.go b/internal/services/managedhsm/internal.go new file mode 100644 index 000000000000..0b204eb545f5 --- /dev/null +++ b/internal/services/managedhsm/internal.go @@ -0,0 +1,131 @@ +package managedhsm + +import ( + "context" + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/Azure/go-autorest/autorest" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type deleteAndPurgeNestedItem interface { + DeleteNestedItem(ctx context.Context) (autorest.Response, error) + NestedItemHasBeenDeleted(ctx context.Context) (autorest.Response, error) + PurgeNestedItem(ctx context.Context) (autorest.Response, error) + NestedItemHasBeenPurged(ctx context.Context) (autorest.Response, error) +} + +func deleteAndOptionallyPurge(ctx context.Context, description string, shouldPurge bool, helper deleteAndPurgeNestedItem) error { + timeout, ok := ctx.Deadline() + if !ok { + return fmt.Errorf("context is missing a timeout") + } + + log.Printf("[DEBUG] Deleting %s..", description) + if resp, err := helper.DeleteNestedItem(ctx); err != nil { + if utils.ResponseWasNotFound(resp) { + return nil + } + + return fmt.Errorf("deleting %s: %+v", description, err) + } + log.Printf("[DEBUG] Waiting for %s to finish deleting..", description) + stateConf := &pluginsdk.StateChangeConf{ + Pending: []string{"InProgress"}, + Target: []string{"NotFound"}, + Refresh: func() (interface{}, string, error) { + item, err := helper.NestedItemHasBeenDeleted(ctx) + if err != nil { + if utils.ResponseWasNotFound(item) { + return item, "NotFound", nil + } + + return nil, "Error", err + } + + return item, "InProgress", nil + }, + ContinuousTargetOccurence: 3, + PollInterval: 5 * time.Second, + Timeout: time.Until(timeout), + } + if _, err := stateConf.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for %s to be deleted: %+v", description, err) + } + log.Printf("[DEBUG] Deleted %s.", description) + + if !shouldPurge { + log.Printf("[DEBUG] Skipping purging of %s as opted-out..", description) + return nil + } + + log.Printf("[DEBUG] Purging %s..", description) + err := pluginsdk.Retry(time.Until(timeout), func() *pluginsdk.RetryError { + _, err := helper.PurgeNestedItem(ctx) + if err == nil { + return nil + } + if strings.Contains(err.Error(), "is currently being deleted") { + return pluginsdk.RetryableError(fmt.Errorf("%s is currently being deleted, retrying", description)) + } + return pluginsdk.NonRetryableError(fmt.Errorf("purging of %s : %+v", description, err)) + }) + if err != nil { + return err + } + + log.Printf("[DEBUG] Waiting for %s to finish purging..", description) + stateConf = &pluginsdk.StateChangeConf{ + Pending: []string{"InProgress"}, + Target: []string{"NotFound"}, + Refresh: func() (interface{}, string, error) { + item, err := helper.NestedItemHasBeenPurged(ctx) + if err != nil { + if utils.ResponseWasNotFound(item) { + return item, "NotFound", nil + } + + return nil, "Error", err + } + + return item, "InProgress", nil + }, + ContinuousTargetOccurence: 3, + PollInterval: 5 * time.Second, + Timeout: time.Until(timeout), + } + if _, err := stateConf.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for %s to finish purging: %+v", description, err) + } + log.Printf("[DEBUG] Purged %s.", description) + + return nil +} + +func managedHSMKeyRefreshFunc(childItemUri string) pluginsdk.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Checking to see if Managed HSM Key %q is available..", childItemUri) + + PTransport := &http.Transport{Proxy: http.ProxyFromEnvironment} + + client := &http.Client{ + Transport: PTransport, + } + + conn, err := client.Get(childItemUri) + if err != nil { + log.Printf("[DEBUG] Didn't find Managed HSM Key at %q", childItemUri) + return nil, "pending", fmt.Errorf("checking Managed HSM Key at %q: %s", childItemUri, err) + } + + defer conn.Body.Close() + + log.Printf("[DEBUG] Found Managed HSM Key %q", childItemUri) + return "available", "available", nil + } +} diff --git a/internal/services/managedhsm/key_vault_managed_hardware_security_module_key_resource.go b/internal/services/managedhsm/key_vault_managed_hardware_security_module_key_resource.go new file mode 100644 index 000000000000..18fd150aa16c --- /dev/null +++ b/internal/services/managedhsm/key_vault_managed_hardware_security_module_key_resource.go @@ -0,0 +1,500 @@ +package managedhsm + +import ( + "context" + "encoding/base64" + "fmt" + "log" + "time" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/date" + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" + "github.com/hashicorp/go-azure-sdk/resource-manager/keyvault/2023-07-01/managedhsms" + "github.com/hashicorp/terraform-provider-azurerm/internal/locks" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tags" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/utils" + "github.com/tombuildsstuff/kermit/sdk/keyvault/7.4/keyvault" +) + +type KeyVaultMHSMKeyResource struct{} + +var _ sdk.ResourceWithUpdate = KeyVaultMHSMKeyResource{} + +func (r KeyVaultMHSMKeyResource) ModelObject() interface{} { + return &KeyVaultMHSMKeyResourceSchema{} +} + +type KeyVaultMHSMKeyResourceSchema struct { + Name string `tfschema:"name"` + ManagedHSMID string `tfschema:"managed_hsm_id"` + KeyType string `tfschema:"key_type"` + KeyOpts []string `tfschema:"key_opts"` + KeySize int `tfschema:"key_size"` + Curve string `tfschema:"curve"` + NotBeforeDate string `tfschema:"not_before_date"` + ExpirationDate string `tfschema:"expiration_date"` + Tags map[string]interface{} `tfschema:"tags"` + VersionedId string `tfschema:"versioned_id"` +} + +func (r KeyVaultMHSMKeyResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.ManagedHSMDataPlaneVersionlessKeyID +} + +func (r KeyVaultMHSMKeyResource) ResourceType() string { + return "azurerm_key_vault_managed_hardware_security_module_key" +} + +func (r KeyVaultMHSMKeyResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + ForceNew: true, + Required: true, + Type: pluginsdk.TypeString, + }, + "managed_hsm_id": { + Type: pluginsdk.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: managedhsms.ValidateManagedHSMID, + }, + + "key_type": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + // turns out Azure's *really* sensitive about the casing of these + // issue: https://github.com/Azure/azure-rest-api-specs/issues/1739 + ValidateFunc: validation.StringInSlice([]string{ + string(keyvault.JSONWebKeyTypeECHSM), + string(keyvault.JSONWebKeyTypeRSAHSM), + }, false), + }, + + "key_size": { + Type: pluginsdk.TypeInt, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"curve"}, + }, + + "curve": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: func(k, old, new string, d *pluginsdk.ResourceData) bool { + return old == "SECP256K1" && new == string(keyvault.JSONWebKeyCurveNameP256K) + }, + ValidateFunc: func() pluginsdk.SchemaValidateFunc { + out := []string{ + string(keyvault.JSONWebKeyCurveNameP256), + string(keyvault.JSONWebKeyCurveNameP256K), + string(keyvault.JSONWebKeyCurveNameP384), + string(keyvault.JSONWebKeyCurveNameP521), + } + return validation.StringInSlice(out, false) + }(), + ExactlyOneOf: []string{"key_size"}, + }, + + "key_opts": { + Type: pluginsdk.TypeSet, + Required: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + // turns out Azure's *really* sensitive about the casing of these + // issue: https://github.com/Azure/azure-rest-api-specs/issues/1739 + ValidateFunc: validation.StringInSlice([]string{ + string(keyvault.JSONWebKeyOperationDecrypt), + string(keyvault.JSONWebKeyOperationEncrypt), + string(keyvault.JSONWebKeyOperationSign), + string(keyvault.JSONWebKeyOperationUnwrapKey), + string(keyvault.JSONWebKeyOperationVerify), + string(keyvault.JSONWebKeyOperationWrapKey), + }, false), + }, + }, + + "not_before_date": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsRFC3339Time, + }, + + "expiration_date": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.IsRFC3339Time, + }, + + "tags": tags.Schema(), + } +} + +func (r KeyVaultMHSMKeyResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "versioned_id": { + Computed: true, + Type: pluginsdk.TypeString, + }, + } +} + +func (r KeyVaultMHSMKeyResource) CustomizeDiff() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + diff := metadata.ResourceDiff + + // if any value has changed, we need to SetNewComputed on versioned_id as any change to the key is a new version + if diff.HasChanges("key_opts", "not_before_date", "tags", "expiration_date") { + return diff.SetNewComputed("versioned_id") + } + + return nil + }, + Timeout: 5 * time.Minute, + } +} + +func (r KeyVaultMHSMKeyResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.ManagedHSMs.DataPlaneKeysClient + domainSuffix, ok := metadata.Client.Account.Environment.ManagedHSM.DomainSuffix() + if !ok { + return fmt.Errorf("could not determine Managed HSM domain suffix for environment %q", metadata.Client.Account.Environment.Name) + } + + var config KeyVaultMHSMKeyResourceSchema + if err := metadata.Decode(&config); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + managedHsmId, err := managedhsms.ParseManagedHSMID(config.ManagedHSMID) + if err != nil { + return err + } + baseUri, err := metadata.Client.ManagedHSMs.BaseUriForManagedHSM(ctx, *managedHsmId) + if err != nil { + return fmt.Errorf("determining the Data Plane Endpoint for %s: %+v", *managedHsmId, err) + } + if baseUri == nil { + return fmt.Errorf("unable to determine the Data Plane Endpoint for %q", *managedHsmId) + } + endpoint, err := parse.ManagedHSMEndpoint(*baseUri, domainSuffix) + if err != nil { + return fmt.Errorf("parsing the Data Plane Endpoint %q: %+v", *endpoint, err) + } + + id := parse.NewManagedHSMDataPlaneVersionlessKeyID(endpoint.ManagedHSMName, endpoint.DomainSuffix, config.Name) + + locks.ByName(managedHsmId.ID(), "azurerm_key_vault_managed_hardware_security_module") + defer locks.UnlockByName(managedHsmId.ID(), "azurerm_key_vault_managed_hardware_security_module") + + existing, err := client.GetKey(ctx, endpoint.BaseURI(), id.KeyName, "") + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for the presence of an existing %s: %+v", id, err) + } + } + if !utils.ResponseWasNotFound(existing.Response) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + parameters := keyvault.KeyCreateParameters{ + Kty: keyvault.JSONWebKeyType(config.KeyType), + KeyOps: expandKeyVaultKeyOptions(config.KeyOpts), + KeyAttributes: &keyvault.KeyAttributes{ + Enabled: utils.Bool(true), + }, + + Tags: tags.Expand(config.Tags), + } + + if config.Curve != "" { + if config.KeyType != string(keyvault.JSONWebKeyTypeECHSM) { + return fmt.Errorf("`key_type` must be `EC-HSM` when `curve` is set") + } + parameters.Curve = keyvault.JSONWebKeyCurveName(config.Curve) + } + + if config.KeySize > 0 { + if config.KeyType != string(keyvault.JSONWebKeyTypeRSAHSM) { + return fmt.Errorf("`key_type` must be `RSA-HSM` when `key_size` is set") + } + parameters.KeySize = pointer.To(int32(config.KeySize)) + } + + if config.NotBeforeDate != "" { + notBeforeDate, _ := time.Parse(time.RFC3339, config.NotBeforeDate) // validated by schema + notBeforeUnixTime := date.UnixTime(notBeforeDate) + parameters.KeyAttributes.NotBefore = ¬BeforeUnixTime + } + + if config.ExpirationDate != "" { + expirationDate, _ := time.Parse(time.RFC3339, config.ExpirationDate) // validated by schema + expirationUnixTime := date.UnixTime(expirationDate) + parameters.KeyAttributes.Expires = &expirationUnixTime + } + + if resp, err := client.CreateKey(ctx, endpoint.BaseURI(), config.Name, parameters); err != nil { + if metadata.Client.Features.KeyVault.RecoverSoftDeletedHSMKeys && utils.ResponseWasConflict(resp.Response) { + recoveredKey, err := client.RecoverDeletedKey(ctx, endpoint.BaseURI(), config.Name) + if err != nil { + return err + } + log.Printf("[DEBUG] Recovering HSM Key %q with ID: %q", config.Name, *recoveredKey.Key.Kid) + if kid := recoveredKey.Key.Kid; kid != nil { + stateConf := &pluginsdk.StateChangeConf{ + Pending: []string{"pending"}, + Target: []string{"available"}, + Refresh: managedHSMKeyRefreshFunc(*kid), + Delay: 30 * time.Second, + PollInterval: 10 * time.Second, + ContinuousTargetOccurence: 10, + Timeout: metadata.ResourceData.Timeout(pluginsdk.TimeoutCreate), + } + + if _, err := stateConf.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for HSM Key %q to become available: %s", config.Name, err) + } + log.Printf("[DEBUG] Key %q recovered with ID: %q", config.Name, *kid) + } + } else { + return fmt.Errorf("Creating Key: %+v", err) + } + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r KeyVaultMHSMKeyResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.ManagedHSMs.DataPlaneKeysClient + domainSuffix, ok := metadata.Client.Account.Environment.ManagedHSM.DomainSuffix() + if !ok { + return fmt.Errorf("could not determine Managed HSM domain suffix for environment %q", metadata.Client.Account.Environment.Name) + } + + schema := KeyVaultMHSMKeyResourceSchema{} + + id, err := parse.ManagedHSMDataPlaneVersionlessKeyID(metadata.ResourceData.Id(), domainSuffix) + if err != nil { + return err + } + + subscriptionId := commonids.NewSubscriptionID(metadata.Client.Account.SubscriptionId) + resourceManagerId, err := metadata.Client.ManagedHSMs.ManagedHSMIDFromBaseUrl(ctx, subscriptionId, id.BaseUri(), domainSuffix) + if err != nil { + return fmt.Errorf("determining Resource Manager ID for %q: %+v", id, err) + } + if resourceManagerId == nil { + return metadata.MarkAsGone(*id) + } + + resp, err := client.GetKey(ctx, id.BaseUri(), id.KeyName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return metadata.MarkAsGone(*id) + } + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + if key := resp.Key; key != nil { + schema.Name = id.KeyName + schema.ManagedHSMID = resourceManagerId.ID() + schema.KeyType = string(key.Kty) + schema.KeyOpts = flattenKeyVaultKeyOptions(key.KeyOps) + schema.Curve = string(key.Crv) + schema.Tags = tags.Flatten(resp.Tags) + schema.VersionedId = pointer.From(key.Kid) + if key.N != nil { + nBytes, err := base64.RawURLEncoding.DecodeString(*key.N) + if err != nil { + return fmt.Errorf("Could not decode N: %+v", err) + } + schema.KeySize = len(nBytes) * 8 + } + + if attributes := resp.Attributes; attributes != nil { + if v := attributes.NotBefore; v != nil { + schema.NotBeforeDate = time.Time(*v).Format(time.RFC3339) + } + + if v := attributes.Expires; v != nil { + schema.ExpirationDate = time.Time(*v).Format(time.RFC3339) + } + } + } + + return metadata.Encode(&schema) + }, + } +} + +func (r KeyVaultMHSMKeyResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.ManagedHSMs.DataPlaneRoleAssignmentsClient + domainSuffix, ok := metadata.Client.Account.Environment.ManagedHSM.DomainSuffix() + if !ok { + return fmt.Errorf("could not determine Managed HSM domain suffix for environment %q", metadata.Client.Account.Environment.Name) + } + + var config KeyVaultMHSMKeyResourceSchema + if err := metadata.Decode(&config); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + id, err := parse.ManagedHSMDataPlaneVersionlessKeyID(metadata.ResourceData.Id(), domainSuffix) + if err != nil { + return err + } + + subscriptionId := commonids.NewSubscriptionID(metadata.Client.Account.SubscriptionId) + resourceManagerId, err := metadata.Client.ManagedHSMs.ManagedHSMIDFromBaseUrl(ctx, subscriptionId, id.BaseUri(), domainSuffix) + if err != nil { + return fmt.Errorf("determining Resource Manager ID for %q: %+v", id, err) + } + if resourceManagerId == nil { + return fmt.Errorf("unable to determine the Resource Manager ID for %s", id) + } + + parameters := keyvault.KeyUpdateParameters{ + KeyOps: expandKeyVaultKeyOptions(config.KeyOpts), + KeyAttributes: &keyvault.KeyAttributes{ + Enabled: utils.Bool(true), + }, + + Tags: tags.Expand(config.Tags), + } + + if config.NotBeforeDate != "" { + notBeforeDate, _ := time.Parse(time.RFC3339, config.NotBeforeDate) // validated by schema + notBeforeUnixTime := date.UnixTime(notBeforeDate) + parameters.KeyAttributes.NotBefore = ¬BeforeUnixTime + } + + if config.ExpirationDate != "" { + expirationDate, _ := time.Parse(time.RFC3339, config.ExpirationDate) // validated by schema + expirationUnixTime := date.UnixTime(expirationDate) + parameters.KeyAttributes.Expires = &expirationUnixTime + } + + if _, err = client.UpdateKey(ctx, id.BaseUri(), config.Name, "", parameters); err != nil { + return err + } + + return nil + }, + } +} + +var _ deleteAndPurgeNestedItem = deleteAndPurgeKey{} + +type deleteAndPurgeKey struct { + client *keyvault.BaseClient + keyVaultUri string + name string +} + +func (d deleteAndPurgeKey) DeleteNestedItem(ctx context.Context) (autorest.Response, error) { + resp, err := d.client.DeleteKey(ctx, d.keyVaultUri, d.name) + return resp.Response, err +} + +func (d deleteAndPurgeKey) NestedItemHasBeenDeleted(ctx context.Context) (autorest.Response, error) { + resp, err := d.client.GetKey(ctx, d.keyVaultUri, d.name, "") + return resp.Response, err +} + +func (d deleteAndPurgeKey) PurgeNestedItem(ctx context.Context) (autorest.Response, error) { + return d.client.PurgeDeletedKey(ctx, d.keyVaultUri, d.name) +} + +func (d deleteAndPurgeKey) NestedItemHasBeenPurged(ctx context.Context) (autorest.Response, error) { + resp, err := d.client.GetDeletedKey(ctx, d.keyVaultUri, d.name) + return resp.Response, err +} + +func (r KeyVaultMHSMKeyResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.ManagedHSMs.DataPlaneKeysClient + hsmClient := metadata.Client.ManagedHSMs.ManagedHsmClient + + domainSuffix, ok := metadata.Client.Account.Environment.ManagedHSM.DomainSuffix() + if !ok { + return fmt.Errorf("could not determine Managed HSM domain suffix for environment %q", metadata.Client.Account.Environment.Name) + } + + id, err := parse.ManagedHSMDataPlaneVersionlessKeyID(metadata.ResourceData.Id(), domainSuffix) + if err != nil { + return err + } + + subscriptionId := commonids.NewSubscriptionID(metadata.Client.Account.SubscriptionId) + resourceManagerId, err := metadata.Client.ManagedHSMs.ManagedHSMIDFromBaseUrl(ctx, subscriptionId, id.BaseUri(), domainSuffix) + if err != nil { + return fmt.Errorf("determining Resource Manager ID for %q: %+v", id, err) + } + if resourceManagerId == nil { + return fmt.Errorf("unable to determine the Resource Manager ID for %s", id) + } + + managedHSM, err := hsmClient.Get(ctx, *resourceManagerId) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", resourceManagerId, err) + } + + shouldPurge := metadata.Client.Features.KeyVault.PurgeSoftDeletedHSMKeysOnDestroy + if shouldPurge && managedHSM.Model != nil && managedHSM.Model.Properties != nil && utils.NormaliseNilableBool(managedHSM.Model.Properties.EnablePurgeProtection) { + log.Printf("[DEBUG] cannot purge key %q because Managed HSM %q has purge protection enabled", id.KeyName, id.ManagedHSMName) + shouldPurge = false + } + + description := fmt.Sprintf("Key %q (Managed HSM %q)", id.KeyName, id.ManagedHSMName) + deleter := deleteAndPurgeKey{ + client: client, + keyVaultUri: id.BaseUri(), + name: id.KeyName, + } + + return deleteAndOptionallyPurge(ctx, description, shouldPurge, deleter) + }, + } +} + +func expandKeyVaultKeyOptions(input []string) *[]keyvault.JSONWebKeyOperation { + results := make([]keyvault.JSONWebKeyOperation, 0, len(input)) + + for _, option := range input { + results = append(results, keyvault.JSONWebKeyOperation(option)) + } + + return &results +} + +func flattenKeyVaultKeyOptions(input *[]string) []string { + results := make([]string, 0) + if input == nil { + return results + } + + return append(results, *input...) +} diff --git a/internal/services/managedhsm/key_vault_managed_hardware_security_module_key_resource_test.go b/internal/services/managedhsm/key_vault_managed_hardware_security_module_key_resource_test.go new file mode 100644 index 000000000000..a7ea2b589156 --- /dev/null +++ b/internal/services/managedhsm/key_vault_managed_hardware_security_module_key_resource_test.go @@ -0,0 +1,344 @@ +package managedhsm_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" + "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/services/managedhsm/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type KeyVaultMHSMKeyTestResource struct{} + +func testAccKeyVaultMHSMKey_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault_managed_hardware_security_module_key", "test") + r := KeyVaultMHSMKeyTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccKeyVaultMHSMKey_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault_managed_hardware_security_module_key", "test") + r := KeyVaultMHSMKeyTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func testAccKeyVaultHSMKey_purge(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault_managed_hardware_security_module_key", "test") + r := KeyVaultMHSMKeyTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + { + Config: r.basic(data), + Destroy: true, + }, + }) +} + +func testAccKeyVaultHSMKey_softDeleteRecovery(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault_managed_hardware_security_module_key", "test") + r := KeyVaultMHSMKeyTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.softDeleteRecovery(data, false), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("not_before_date").HasValue("2020-01-01T01:02:03Z"), + check.That(data.ResourceName).Key("expiration_date").HasValue("2021-01-01T01:02:03Z"), + check.That(data.ResourceName).Key("tags.%").HasValue("1"), + check.That(data.ResourceName).Key("tags.hello").HasValue("world"), + ), + }, + data.ImportStep("key_size", "key_vault_id"), + { + Config: r.softDeleteRecovery(data, false), + Destroy: true, + }, + { + Config: r.softDeleteRecovery(data, true), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("not_before_date").HasValue("2020-01-01T01:02:03Z"), + check.That(data.ResourceName).Key("expiration_date").HasValue("2021-01-01T01:02:03Z"), + check.That(data.ResourceName).Key("tags.%").HasValue("1"), + check.That(data.ResourceName).Key("tags.hello").HasValue("world"), + ), + }, + }) +} + +func (r KeyVaultMHSMKeyTestResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + domainSuffix, ok := clients.Account.Environment.ManagedHSM.DomainSuffix() + if !ok { + return nil, fmt.Errorf("could not determine Managed HSM domain suffix for environment %q", clients.Account.Environment.Name) + } + id, err := parse.ManagedHSMDataPlaneVersionlessKeyID(state.ID, domainSuffix) + if err != nil { + return nil, err + } + + subscriptionId := commonids.NewSubscriptionID(clients.Account.SubscriptionId) + resourceManagerId, err := clients.ManagedHSMs.ManagedHSMIDFromBaseUrl(ctx, subscriptionId, id.BaseUri(), domainSuffix) + if err != nil { + return nil, fmt.Errorf("determining Resource Manager ID for %q: %+v", id, err) + } + if resourceManagerId == nil { + return nil, fmt.Errorf("unable to determine the Resource Manager ID for %s", id) + } + + resp, err := clients.ManagedHSMs.DataPlaneKeysClient.GetKey(ctx, id.BaseUri(), id.KeyName, "") + if err != nil { + return nil, fmt.Errorf("reading %s: %+v", *id, err) + } + + return utils.Bool(resp.Key != nil), nil +} + +func (r KeyVaultMHSMKeyTestResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +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 = "EC-HSM" + curve = "P-521" + key_opts = ["sign"] + + depends_on = [ + azurerm_key_vault_managed_hardware_security_module_role_assignment.test, + azurerm_key_vault_managed_hardware_security_module_role_assignment.test1 + ] +} +`, r.template(data), data.RandomString) +} + +func (r KeyVaultMHSMKeyTestResource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +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 = "EC-HSM" + curve = "P-521" + key_opts = ["sign", "verify"] + + not_before_date = "2020-01-01T01:02:03Z" + expiration_date = "2021-01-01T01:02:03Z" + + tags = { + "hello" = "world" + } + + depends_on = [ + azurerm_key_vault_managed_hardware_security_module_role_assignment.test, + azurerm_key_vault_managed_hardware_security_module_role_assignment.test1 + ] +} +`, r.template(data), data.RandomString) +} + +func (r KeyVaultMHSMKeyTestResource) softDeleteRecovery(data acceptance.TestData, purge bool) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_deleted_hardware_security_module_keys_on_destroy = %t + } + } +} + +%s + +resource "azurerm_key_vault_managed_hardware_security_module_key" "test" { + name = "acctestHSMK-%[3]s" + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id + key_type = "EC-HSM" + curve = "P-521" + key_opts = ["sign"] + + not_before_date = "2020-01-01T01:02:03Z" + expiration_date = "2021-01-01T01:02:03Z" + + tags = { + "hello" = "world" + } + + depends_on = [ + azurerm_key_vault_managed_hardware_security_module_role_assignment.test, + azurerm_key_vault_managed_hardware_security_module_role_assignment.test1 + ] +} +`, purge, r.template(data), data.RandomString) +} + +func (r KeyVaultMHSMKeyTestResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" { +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-KV-%[1]s" + location = "%[2]s" +} + +resource "azurerm_key_vault" "test" { + name = "acc%[3]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 + 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%[3]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 = false + + security_domain_key_vault_certificate_ids = [for cert in azurerm_key_vault_certificate.cert : cert.id] + security_domain_quorum = 3 +} + +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "test" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + 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" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + 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 +} +`, data.RandomString, data.Locations.Primary, data.RandomInteger) +} diff --git a/internal/services/managedhsm/key_vault_managed_hardware_security_module_resource.go b/internal/services/managedhsm/key_vault_managed_hardware_security_module_resource.go index 79d19a835423..3928820e88a7 100644 --- a/internal/services/managedhsm/key_vault_managed_hardware_security_module_resource.go +++ b/internal/services/managedhsm/key_vault_managed_hardware_security_module_resource.go @@ -25,7 +25,6 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/clients" keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" keyVaultValidation "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" - "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/client" "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/custompollers" managedHSMValidation "github.com/hashicorp/terraform-provider-azurerm/internal/services/managedhsm/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -246,7 +245,9 @@ func resourceArmKeyVaultManagedHardwareSecurityModuleCreate(d *pluginsdk.Resourc if err != nil || resp.Model == nil || resp.Model.Properties == nil || resp.Model.Properties.HsmUri == nil { return fmt.Errorf("got nil HSMUri for %s: %+v", id, err) } - encData, err := securityDomainDownload(ctx, client, *resp.Model.Properties.HsmUri, d.Get("security_domain_key_vault_certificate_ids").([]interface{}), d.Get("security_domain_quorum").(int)) + + keyVaultClient := meta.(*clients.Client).KeyVault.ManagementClient + encData, err := securityDomainDownload(ctx, client.DataPlaneSecurityDomainsClient, *keyVaultClient, *resp.Model.Properties.HsmUri, d.Get("security_domain_key_vault_certificate_ids").([]interface{}), d.Get("security_domain_quorum").(int)) if err != nil { return fmt.Errorf("downloading security domain for %q: %+v", id, err) } @@ -296,7 +297,9 @@ func resourceArmKeyVaultManagedHardwareSecurityModuleUpdate(d *pluginsdk.Resourc if err != nil || resp.Model == nil || resp.Model.Properties == nil || resp.Model.Properties.HsmUri == nil { return fmt.Errorf("got nil HSMUri for %s: %+v", id, err) } - encData, err := securityDomainDownload(ctx, kvClient, *resp.Model.Properties.HsmUri, d.Get("security_domain_key_vault_certificate_ids").([]interface{}), d.Get("security_domain_quorum").(int)) + + keyVaultClient := meta.(*clients.Client).KeyVault.ManagementClient + encData, err := securityDomainDownload(ctx, kvClient.DataPlaneSecurityDomainsClient, *keyVaultClient, *resp.Model.Properties.HsmUri, d.Get("security_domain_key_vault_certificate_ids").([]interface{}), d.Get("security_domain_quorum").(int)) if err != nil { return fmt.Errorf("downloading security domain for %q: %+v", id, err) } @@ -458,10 +461,7 @@ func flattenMHSMNetworkAcls(acl *managedhsms.MHSMNetworkRuleSet) []interface{} { } } -func securityDomainDownload(ctx context.Context, cli *client.Client, vaultBaseUrl string, certIds []interface{}, quorum int) (encDataStr string, err error) { - sdClient := cli.DataPlaneSecurityDomainsClient - keyClient := cli.DataPlaneClient - +func securityDomainDownload(ctx context.Context, sdClient *kv74.HSMSecurityDomainClient, keyClient kv74.BaseClient, vaultBaseUrl string, certIds []interface{}, quorum int) (encDataStr string, err error) { var param kv74.CertificateInfoObject param.Required = utils.Int32(int32(quorum)) diff --git a/internal/services/managedhsm/key_vault_managed_hardware_security_module_resource_test.go b/internal/services/managedhsm/key_vault_managed_hardware_security_module_resource_test.go index a6dc3686491e..2190c940fb2f 100644 --- a/internal/services/managedhsm/key_vault_managed_hardware_security_module_resource_test.go +++ b/internal/services/managedhsm/key_vault_managed_hardware_security_module_resource_test.go @@ -50,6 +50,12 @@ func TestAccKeyVaultManagedHardwareSecurityModule(t *testing.T) { "basic": testAccDataSourceKeyVaultManagedHardwareSecurityModuleRoleDefinition_basic, "legacy": testAccDataSourceKeyVaultManagedHardwareSecurityModuleRoleDefinition_legacy, }, + "keys": { + "basic": testAccKeyVaultMHSMKey_basic, + "complete": testAccKeyVaultMHSMKey_complete, + "purge": testAccKeyVaultHSMKey_purge, + "softDeleteRecovery": testAccKeyVaultHSMKey_softDeleteRecovery, + }, }) } diff --git a/internal/services/managedhsm/parse/managed_hsm_data_plane_versioned_key.go b/internal/services/managedhsm/parse/managed_hsm_data_plane_versioned_key.go index 09c9168f48de..a634ac4384bb 100644 --- a/internal/services/managedhsm/parse/managed_hsm_data_plane_versioned_key.go +++ b/internal/services/managedhsm/parse/managed_hsm_data_plane_versioned_key.go @@ -85,3 +85,14 @@ func (id ManagedHSMDataPlaneVersionedKeyId) BaseUri() string { func (id ManagedHSMDataPlaneVersionedKeyId) ID() string { return fmt.Sprintf("https://%s.%s/keys/%s/%s", id.ManagedHSMName, id.DomainSuffix, id.KeyName, id.KeyVersion) } + +// String returns a human-readable description of this Managed HSM Key ID +func (id ManagedHSMDataPlaneVersionedKeyId) String() string { + components := []string{ + fmt.Sprintf("Managed HSM Name: %q", id.ManagedHSMName), + fmt.Sprintf("Domain Suffix: %q", id.DomainSuffix), + fmt.Sprintf("Key Name: %q", id.KeyName), + fmt.Sprintf("Key Version: %q", id.KeyVersion), + } + return fmt.Sprintf("Managed HSM Key (%s)", strings.Join(components, "\n")) +} diff --git a/internal/services/managedhsm/parse/managed_hsm_data_plane_versionless_key.go b/internal/services/managedhsm/parse/managed_hsm_data_plane_versionless_key.go index 63ad4789c7cc..8282a14b592c 100644 --- a/internal/services/managedhsm/parse/managed_hsm_data_plane_versionless_key.go +++ b/internal/services/managedhsm/parse/managed_hsm_data_plane_versionless_key.go @@ -80,3 +80,13 @@ func (id ManagedHSMDataPlaneVersionlessKeyId) BaseUri() string { func (id ManagedHSMDataPlaneVersionlessKeyId) ID() string { return fmt.Sprintf("https://%s.%s/keys/%s", id.ManagedHSMName, id.DomainSuffix, id.KeyName) } + +// String returns a human-readable description of this Managed HSM Key ID +func (id ManagedHSMDataPlaneVersionlessKeyId) String() string { + components := []string{ + fmt.Sprintf("Managed HSM Name: %q", id.ManagedHSMName), + fmt.Sprintf("Domain Suffix: %q", id.DomainSuffix), + fmt.Sprintf("Key Name: %q", id.KeyName), + } + return fmt.Sprintf("Managed HSM Versionless Key (%s)", strings.Join(components, "\n")) +} diff --git a/internal/services/managedhsm/registration.go b/internal/services/managedhsm/registration.go index 61c233c2b596..304f69bee06a 100644 --- a/internal/services/managedhsm/registration.go +++ b/internal/services/managedhsm/registration.go @@ -54,6 +54,7 @@ func (r Registration) DataSources() []sdk.DataSource { func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ + KeyVaultMHSMKeyResource{}, KeyVaultMHSMRoleDefinitionResource{}, KeyVaultManagedHSMRoleAssignmentResource{}, } diff --git a/internal/services/storage/storage_account_resource_test.go b/internal/services/storage/storage_account_resource_test.go index b277af15edf2..4b19fa76bd56 100644 --- a/internal/services/storage/storage_account_resource_test.go +++ b/internal/services/storage/storage_account_resource_test.go @@ -1411,6 +1411,21 @@ func TestAccStorageAccount_updateToUsingIdentityAndCustomerManagedKey(t *testing }) } +func TestAccStorageAccount_customerManagedKeyForHSM(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_storage_account", "test") + r := StorageAccountResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.customerManagedKeyForHSM(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccStorageAccount_edgeZone(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_storage_account", "test") r := StorageAccountResource{} @@ -4350,6 +4365,37 @@ resource "azurerm_storage_account" "test" { `, r.cmkTemplate(data), data.RandomString) } +func (r StorageAccountResource) customerManagedKeyForHSM(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "test" { + name = "unlikely23exst2acct%[2]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "LRS" + account_kind = "StorageV2" + + identity { + type = "UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id, + ] + } + + customer_managed_key { + managed_hsm_key_id = azurerm_key_vault_managed_hardware_security_module_key.test.id + user_assigned_identity_id = azurerm_user_assigned_identity.test.id + } + + depends_on = [ + azurerm_key_vault_managed_hardware_security_module_role_assignment.user, + ] +} +`, r.hsmKeyTemplate(data), data.RandomString) +} + func (r StorageAccountResource) customerManagedKeyRemoteKeyVault(data acceptance.TestData) string { clientData := data.Client() return fmt.Sprintf(` @@ -4996,3 +5042,148 @@ resource "azurerm_storage_account" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomString, repl) } + +func (r StorageAccountResource) hsmKeyTemplate(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" { +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-KV-%[1]s" + location = "%[2]s" +} + +resource "azurerm_key_vault" "test" { + name = "acc%[3]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 + 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%[3]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 = 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%[1]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" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + 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" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + 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 +} + +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "user" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + 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-%[1]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 + ] +} +`, data.RandomString, data.Locations.Primary, data.RandomInteger) +} diff --git a/website/docs/guides/features-block.html.markdown b/website/docs/guides/features-block.html.markdown index 5abf5a0f80a3..f52c38871fbd 100644 --- a/website/docs/guides/features-block.html.markdown +++ b/website/docs/guides/features-block.html.markdown @@ -178,13 +178,17 @@ The `key_vault` block supports the following: * `purge_soft_deleted_hardware_security_modules_on_destroy` - (Optional) Should the `azurerm_key_vault_managed_hardware_security_module` resource be permanently deleted (e.g. purged) when destroyed? Defaults to `true`. +* `purge_soft_deleted_hardware_security_module_keys_on_destroy` - (Optional) Should the `azurerm_key_vault_managed_hardware_security_module_key` resource be permanently deleted (e.g. purged) when destroyed? Defaults to `true`. + * `recover_soft_deleted_certificates` - (Optional) Should the `azurerm_key_vault_certificate` resource recover a Soft-Deleted Certificate? Defaults to `true`. * `recover_soft_deleted_key_vaults` - (Optional) Should the `azurerm_key_vault` resource recover a Soft-Deleted Key Vault? Defaults to `true`. * `recover_soft_deleted_keys` - (Optional) Should the `azurerm_key_vault_key` resource recover a Soft-Deleted Key? Defaults to `true`. -* `recover_soft_deleted_secrets` - (Optional) Should the `azurerm_key_vault_secret` resource recover a Soft-Deleted Secret? Defaults to `true`. +* `recover_soft_deleted_secrets` - (Optional) Should the `azurerm_key_vault_secret` resource recover a Soft-Deleted Secret? Defaults to `true` + +* `recover_soft_deleted_hardware_security_module_keys` - (Optional) Should the `azurerm_key_vault_managed_hardware_security_module_key` resource recover a Soft-Deleted Key? Defaults to `true`. ~> **Note:** When recovering soft-deleted Key Vault items (Keys, Certificates, and Secrets) the Principal used by Terraform needs the `"recover"` permission. diff --git a/website/docs/r/key_vault_managed_hardware_security_module_key.html.markdown b/website/docs/r/key_vault_managed_hardware_security_module_key.html.markdown new file mode 100644 index 000000000000..1d5b882f16bf --- /dev/null +++ b/website/docs/r/key_vault_managed_hardware_security_module_key.html.markdown @@ -0,0 +1,117 @@ +--- +subcategory: "Key Vault" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_key_vault_managed_hardware_security_module_key" +description: |- + Manages a Key Vault Managed Hardware Security Module Key. +--- + +# azurerm_key_vault_managed_hardware_security_module_key + +Manages a Key Vault Managed Hardware Security Module Key. + +~> **Note:** The Azure Provider includes a Feature Toggle which will purge a Key Vault Managed Hardware Security Module Key resource on destroy, rather than the default soft-delete. See [`purge_soft_deleted_hardware_security_modules_on_destroy`](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/features-block#purge_soft_deleted_hardware_security_module_keys_on_destroy) for more information. + +```hcl +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault_managed_hardware_security_module" "example" { + name = "example" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.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 + + active_config { + security_domain_certificate = [ + azurerm_key_vault_certificate.cert[0].id, + azurerm_key_vault_certificate.cert[1].id, + azurerm_key_vault_certificate.cert[2].id, + ] + security_domain_quorum = 2 + } +} + +// this gives your service principal the HSM Crypto User role which lets you create and destroy hsm keys +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "hsm-crypto-user" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + 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 +} + +// this gives your service principal the HSM Crypto Officer role which lets you purge hsm keys +resource "azurerm_key_vault_managed_hardware_security_module_role_assignment" "hsm-crypto-officer" { + vault_base_url = azurerm_key_vault_managed_hardware_security_module.test.hsm_uri + 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 +} + +resource "azurerm_key_vault_managed_hardware_security_module_key" "example" { + name = "example" + managed_hsm_id = azurerm_key_vault_managed_hardware_security_module.test.id + key_type = "EC-HSM" + curve = "P-521" + key_opts = ["sign"] + + depends_on = [ + azurerm_key_vault_managed_hardware_security_module_role_assignment.test, + azurerm_key_vault_managed_hardware_security_module_role_assignment.test1 + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Key Vault Managed Hardware Security Module Key. Changing this forces a new resource to be created. + +* `managed_hsm_id` - (Required) Specifies the ID of the Key Vault Managed Hardware Security Module that they key will be owned by. Changing this forces a new resource to be created. + +* `key_type` - (Required) Specifies the Key Type to use for this Key Vault Managed Hardware Security Module Key. Possible values are `EC-HSM` and `RSA-HSM`. Changing this forces a new resource to be created. + +* `key_size` - (Optional) Specifies the Size of the RSA key to create in bytes. For example, 1024 or 2048. *Note*: This field is required if `key_type` is `RSA-HSM`. Changing this forces a new resource to be created. + +* `curve` - (Optional) Specifies the curve to use when creating an `EC-HSM` key. Possible values are `P-256`, `P-256K`, `P-384`, and `P-521`. This field is required if `key_type` is `EC-HSM`. Changing this forces a new resource to be created. + +* `key_opts` - (Required) A list of JSON web key operations. Possible values include: `decrypt`, `encrypt`, `sign`, `unwrapKey`, `verify` and `wrapKey`. Please note these values are case-sensitive. + +* `not_before_date` - (Optional) Key not usable before the provided UTC datetime (Y-m-d'T'H:M:S'Z'). + +~> **Note:** Once `expiration_date` is set, it's not possible to unset the key even if it is deleted & recreated as underlying Azure API uses the restore of the purged key. + +* `expiration_date` - (Optional) Expiration UTC datetime (Y-m-d'T'H:M:S'Z'). When this parameter gets changed on reruns, if newer date is ahead of current date, an update is performed. If the newer date is before the current date, resource will be force created. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The Key Vault Secret Managed Hardware Security Module Key ID. + +* `versioned_id` - The versioned Key Vault Secret Managed Hardware Security Module Key ID. + + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 60 minutes) Used when creating the Key Vault Managed Hardware Security Module Key. +* `update` - (Defaults to 30 minutes) Used when updating the Key Vault Managed Hardware Security Module Key. +* `read` - (Defaults to 5 minutes) Used when retrieving the Key Vault Managed Hardware Security Module Key. +* `delete` - (Defaults to 60 minutes) Used when deleting the Key Vault Managed Hardware Security Module Key. + +## Import + +Key Vault Managed Hardware Security Module Key can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_key_vault_managed_hardware_security_module_key.example https://exampleHSM.managedhsm.azure.net/keys/exampleKey +```