diff --git a/.changelog/26997.txt b/.changelog/26997.txt new file mode 100644 index 00000000000..c96af680ecc --- /dev/null +++ b/.changelog/26997.txt @@ -0,0 +1,3 @@ +```release-note:new-resource + aws_kms_custom_key_store +``` \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5bf75c2a60f..6e9940f896c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1660,6 +1660,7 @@ func New(_ context.Context) (*schema.Provider, error) { "aws_kms_alias": kms.ResourceAlias(), "aws_kms_ciphertext": kms.ResourceCiphertext(), + "aws_kms_custom_key_store": kms.ResourceCustomKeyStore(), "aws_kms_external_key": kms.ResourceExternalKey(), "aws_kms_grant": kms.ResourceGrant(), "aws_kms_key": kms.ResourceKey(), diff --git a/internal/service/kms/custom_key_store.go b/internal/service/kms/custom_key_store.go new file mode 100644 index 00000000000..a1194db5f38 --- /dev/null +++ b/internal/service/kms/custom_key_store.go @@ -0,0 +1,158 @@ +package kms + +import ( + "context" + "errors" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/kms" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func ResourceCustomKeyStore() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceCustomKeyStoreCreate, + ReadWithoutTimeout: resourceCustomKeyStoreRead, + UpdateWithoutTimeout: resourceCustomKeyStoreUpdate, + DeleteWithoutTimeout: resourceCustomKeyStoreDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(15 * time.Minute), + Update: schema.DefaultTimeout(15 * time.Minute), + Delete: schema.DefaultTimeout(15 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "cloud_hsm_cluster_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "custom_key_store_name": { + Type: schema.TypeString, + Required: true, + }, + "key_store_password": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringLenBetween(7, 32)), + }, + "trust_anchor_certificate": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +const ( + ResNameCustomKeyStore = "Custom Key Store" +) + +func resourceCustomKeyStoreCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).KMSConn + + in := &kms.CreateCustomKeyStoreInput{ + CloudHsmClusterId: aws.String(d.Get("cloud_hsm_cluster_id").(string)), + CustomKeyStoreName: aws.String(d.Get("custom_key_store_name").(string)), + KeyStorePassword: aws.String(d.Get("key_store_password").(string)), + TrustAnchorCertificate: aws.String(d.Get("trust_anchor_certificate").(string)), + } + + out, err := conn.CreateCustomKeyStoreWithContext(ctx, in) + if err != nil { + return create.DiagError(names.KMS, create.ErrActionCreating, ResNameCustomKeyStore, d.Get("custom_key_store_name").(string), err) + } + + if out == nil { + return create.DiagError(names.KMS, create.ErrActionCreating, ResNameCustomKeyStore, d.Get("custom_key_store_name").(string), errors.New("empty output")) + } + + d.SetId(aws.StringValue(out.CustomKeyStoreId)) + + return resourceCustomKeyStoreRead(ctx, d, meta) +} + +func resourceCustomKeyStoreRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).KMSConn + + out, err := FindCustomKeyStoreByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] KMS CustomKeyStore (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.KMS, create.ErrActionReading, ResNameCustomKeyStore, d.Id(), err) + } + + d.Set("cloud_hsm_cluster_id", out.CloudHsmClusterId) + d.Set("custom_key_store_name", out.CustomKeyStoreName) + d.Set("trust_anchor_certificate", out.TrustAnchorCertificate) + + return nil +} + +func resourceCustomKeyStoreUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).KMSConn + + update := false + + in := &kms.UpdateCustomKeyStoreInput{ + CustomKeyStoreId: aws.String(d.Id()), + CloudHsmClusterId: aws.String(d.Get("cloud_hsm_cluster_id").(string)), + } + + if d.HasChange("key_store_password") { + in.KeyStorePassword = aws.String(d.Get("key_store_password").(string)) + update = true + } + + if d.HasChange("custom_key_store_name") { + in.NewCustomKeyStoreName = aws.String(d.Get("custom_key_store_name").(string)) + update = true + } + + if !update { + return nil + } + + log.Printf("[DEBUG] Updating KMS CustomKeyStore (%s): %#v", d.Id(), in) + _, err := conn.UpdateCustomKeyStoreWithContext(ctx, in) + if err != nil { + return create.DiagError(names.KMS, create.ErrActionUpdating, ResNameCustomKeyStore, d.Id(), err) + } + + return resourceCustomKeyStoreRead(ctx, d, meta) +} + +func resourceCustomKeyStoreDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).KMSConn + + log.Printf("[INFO] Deleting KMS CustomKeyStore %s", d.Id()) + + _, err := conn.DeleteCustomKeyStoreWithContext(ctx, &kms.DeleteCustomKeyStoreInput{ + CustomKeyStoreId: aws.String(d.Id()), + }) + + if tfawserr.ErrCodeEquals(err, kms.ErrCodeNotFoundException) { + return nil + } + + return nil +} diff --git a/internal/service/kms/custom_key_store_test.go b/internal/service/kms/custom_key_store_test.go new file mode 100644 index 00000000000..b040f917ae4 --- /dev/null +++ b/internal/service/kms/custom_key_store_test.go @@ -0,0 +1,225 @@ +package kms_test + +import ( + "context" + "errors" + "fmt" + "os" + "testing" + + "github.com/aws/aws-sdk-go/service/kms" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tfkms "github.com/hashicorp/terraform-provider-aws/internal/service/kms" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccCustomKeyStore_basic(t *testing.T) { + if os.Getenv("CLOUD_HSM_CLUSTER_ID") == "" { + t.Skip("CLOUD_HSM_CLUSTER_ID environment variable not set") + } + + if os.Getenv("TRUST_ANCHOR_CERTIFICATE") == "" { + t.Skip("TRUST_ANCHOR_CERTIFICATE environment variable not set") + } + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var customkeystore kms.CustomKeyStoresListEntry + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_kms_custom_key_store.test" + + clusterId := os.Getenv("CLOUD_HSM_CLUSTER_ID") + trustAnchorCertificate := os.Getenv("TRUST_ANCHOR_CERTIFICATE") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(kms.EndpointsID, t) + testAccCustomKeyStoresPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomKeyStoreDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCustomKeyStoreConfig_basic(rName, clusterId, trustAnchorCertificate), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomKeyStoreExists(resourceName, &customkeystore), + resource.TestCheckResourceAttr(resourceName, "cloud_hsm_cluster_id", clusterId), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"key_store_password"}, + }, + }, + }) +} + +func testAccCustomKeyStore_update(t *testing.T) { + if os.Getenv("CLOUD_HSM_CLUSTER_ID") == "" { + t.Skip("CLOUD_HSM_CLUSTER_ID environment variable not set") + } + + if os.Getenv("TRUST_ANCHOR_CERTIFICATE") == "" { + t.Skip("TRUST_ANCHOR_CERTIFICATE environment variable not set") + } + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var customkeystore kms.CustomKeyStoresListEntry + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_kms_custom_key_store.test" + + clusterId := os.Getenv("CLOUD_HSM_CLUSTER_ID") + trustAnchorCertificate := os.Getenv("TRUST_ANCHOR_CERTIFICATE") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(kms.EndpointsID, t) + testAccCustomKeyStoresPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomKeyStoreDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCustomKeyStoreConfig_basic(fmt.Sprintf("%s-updated", rName), clusterId, trustAnchorCertificate), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomKeyStoreExists(resourceName, &customkeystore), + resource.TestCheckResourceAttr(resourceName, "cloud_hsm_cluster_id", clusterId), + resource.TestCheckResourceAttr(resourceName, "custom_key_store_name", fmt.Sprintf("%s-updated", rName)), + ), + }, + }, + }) +} + +func testAccCustomKeyStore_disappears(t *testing.T) { + if os.Getenv("CLOUD_HSM_CLUSTER_ID") == "" { + t.Skip("CLOUD_HSM_CLUSTER_ID environment variable not set") + } + + if os.Getenv("TRUST_ANCHOR_CERTIFICATE") == "" { + t.Skip("TRUST_ANCHOR_CERTIFICATE environment variable not set") + } + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var customkeystore kms.CustomKeyStoresListEntry + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_kms_custom_key_store.test" + + clusterId := os.Getenv("CLOUD_HSM_CLUSTER_ID") + trustAnchorCertificate := os.Getenv("TRUST_ANCHOR_CERTIFICATE") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(kms.EndpointsID, t) + testAccCustomKeyStoresPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, kms.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCustomKeyStoreDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCustomKeyStoreConfig_basic(rName, clusterId, trustAnchorCertificate), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomKeyStoreExists(resourceName, &customkeystore), + acctest.CheckResourceDisappears(acctest.Provider, tfkms.ResourceCustomKeyStore(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckCustomKeyStoreDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).KMSConn + ctx := context.Background() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_kms_custom_key_store" { + continue + } + + _, err := tfkms.FindCustomKeyStoreByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + return create.Error(names.KMS, create.ErrActionCheckingDestroyed, tfkms.ResNameCustomKeyStore, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil +} + +func testAccCheckCustomKeyStoreExists(name string, customkeystore *kms.CustomKeyStoresListEntry) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.KMS, create.ErrActionCheckingExistence, tfkms.ResNameCustomKeyStore, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.KMS, create.ErrActionCheckingExistence, tfkms.ResNameCustomKeyStore, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).KMSConn + ctx := context.Background() + resp, err := tfkms.FindCustomKeyStoreByID(ctx, conn, rs.Primary.ID) + + if err != nil { + return create.Error(names.KMS, create.ErrActionCheckingExistence, tfkms.ResNameCustomKeyStore, rs.Primary.ID, err) + } + + *customkeystore = *resp + + return nil + } +} + +func testAccCustomKeyStoresPreCheck(t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).KMSConn + ctx := context.Background() + + input := &kms.DescribeCustomKeyStoresInput{} + _, err := conn.DescribeCustomKeyStoresWithContext(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccCustomKeyStoreConfig_basic(rName, clusterId, anchorCertificate string) string { + return fmt.Sprintf(` +resource "aws_kms_custom_key_store" "test" { + cloud_hsm_cluster_id = %[2]q + custom_key_store_name = %[1]q + key_store_password = "noplaintextpasswords1" + + trust_anchor_certificate = file(%[3]q) +} +`, rName, clusterId, anchorCertificate) +} diff --git a/internal/service/kms/find.go b/internal/service/kms/find.go index 9810691a945..71701006a4b 100644 --- a/internal/service/kms/find.go +++ b/internal/service/kms/find.go @@ -1,6 +1,8 @@ package kms import ( + "context" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/kms" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" @@ -39,6 +41,29 @@ func FindAliasByName(conn *kms.KMS, name string) (*kms.AliasListEntry, error) { return output, nil } +func FindCustomKeyStoreByID(ctx context.Context, conn *kms.KMS, id string) (*kms.CustomKeyStoresListEntry, error) { + in := &kms.DescribeCustomKeyStoresInput{ + CustomKeyStoreId: aws.String(id), + } + out, err := conn.DescribeCustomKeyStoresWithContext(ctx, in) + + if tfawserr.ErrCodeEquals(err, kms.ErrCodeCustomKeyStoreNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + if err != nil { + return nil, err + } + + if out == nil || out.CustomKeyStores[0] == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out.CustomKeyStores[0], nil +} + func FindKeyByID(conn *kms.KMS, id string) (*kms.KeyMetadata, error) { input := &kms.DescribeKeyInput{ KeyId: aws.String(id), diff --git a/internal/service/kms/kms_test.go b/internal/service/kms/kms_test.go new file mode 100644 index 00000000000..a7b6b5339af --- /dev/null +++ b/internal/service/kms/kms_test.go @@ -0,0 +1,27 @@ +package kms_test + +import ( + "testing" +) + +func TestAccKMS_serial(t *testing.T) { + testCases := map[string]map[string]func(t *testing.T){ + "CustomKeyStore": { + "basic": testAccCustomKeyStore_basic, + "update": testAccCustomKeyStore_update, + "disappears": testAccCustomKeyStore_disappears, + }, + } + + for group, m := range testCases { + m := m + t.Run(group, func(t *testing.T) { + for name, tc := range m { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } + }) + } +} diff --git a/website/docs/r/kms_custom_key_store.html.markdown b/website/docs/r/kms_custom_key_store.html.markdown new file mode 100644 index 00000000000..0dea0c400dc --- /dev/null +++ b/website/docs/r/kms_custom_key_store.html.markdown @@ -0,0 +1,56 @@ +--- +subcategory: "KMS (Key Management)" +layout: "aws" +page_title: "AWS: aws_kms_custom_key_store" +description: |- + Terraform resource for managing an AWS KMS (Key Management) Custom Key Store. +--- + +# Resource: aws_kms_custom_key_store + +Terraform resource for managing an AWS KMS (Key Management) Custom Key Store. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_kms_custom_key_store" "test" { + cloud_hsm_cluster_id = var.clous_hsm_cluster_id + custom_key_store_name = "kms-custom-key-store-test" + key_store_password = "noplaintextpasswords1" + + trust_anchor_certificate = file("anchor-certificate.crt") +} +``` + +## Argument Reference + +The following arguments are required: + +* `cloud_hsm_cluster_id` - (Required) Cluster ID of CloudHSM. +* `custom_key_store_name` - (Required) Unique name for Custom Key Store. +* `key_store_password` - (Required) Password for `kmsuser` on CloudHSM. +* `trust_anchor_certificate` - (Required) Customer certificate used for signing on CloudHSM. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The Custom Key Store ID + +## Timeouts + +[Configuration options](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts): + +* `create` - (Default `15m`) +* `update` - (Default `15m`) +* `delete` - (Default `15m`) + +## Import + +KMS (Key Management) Custom Key Store can be imported using the `id`, e.g., + +``` +$ terraform import aws_kms_custom_key_store.example cks-5ebd4ef395a96288e +```