diff --git a/.changelog/24906.txt b/.changelog/24906.txt new file mode 100644 index 00000000000..a3ec8fa7dd0 --- /dev/null +++ b/.changelog/24906.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_redshift_hsm_client_certificate +``` diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 199bc3d5756..eff0fdf37cf 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1763,6 +1763,7 @@ func Provider() *schema.Provider { "aws_redshift_cluster": redshift.ResourceCluster(), "aws_redshift_event_subscription": redshift.ResourceEventSubscription(), + "aws_redshift_hsm_client_certificate": redshift.ResourceHsmClientCertificate(), "aws_redshift_parameter_group": redshift.ResourceParameterGroup(), "aws_redshift_scheduled_action": redshift.ResourceScheduledAction(), "aws_redshift_security_group": redshift.ResourceSecurityGroup(), diff --git a/internal/service/redshift/find.go b/internal/service/redshift/find.go index 66efdb09093..1d754a52aa2 100644 --- a/internal/service/redshift/find.go +++ b/internal/service/redshift/find.go @@ -115,3 +115,31 @@ func FindScheduleAssociationById(conn *redshift.Redshift, id string) (string, *r return aws.StringValue(snapshotSchedule.ScheduleIdentifier), associatedCluster, nil } + +func FindHsmClientCertificateByID(conn *redshift.Redshift, id string) (*redshift.HsmClientCertificate, error) { + input := redshift.DescribeHsmClientCertificatesInput{ + HsmClientCertificateIdentifier: aws.String(id), + } + + out, err := conn.DescribeHsmClientCertificates(&input) + if tfawserr.ErrCodeEquals(err, redshift.ErrCodeHsmClientCertificateNotFoundFault) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if out == nil || len(out.HsmClientCertificates) == 0 { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(out.HsmClientCertificates); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return out.HsmClientCertificates[0], nil +} diff --git a/internal/service/redshift/hsm_client_certificate.go b/internal/service/redshift/hsm_client_certificate.go new file mode 100644 index 00000000000..baf70b877fa --- /dev/null +++ b/internal/service/redshift/hsm_client_certificate.go @@ -0,0 +1,149 @@ +package redshift + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/redshift" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceHsmClientCertificate() *schema.Resource { + return &schema.Resource{ + Create: resourceHsmClientCertificateCreate, + Read: resourceHsmClientCertificateRead, + Update: resourceHsmClientCertificateUpdate, + Delete: resourceHsmClientCertificateDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "hsm_client_certificate_identifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "hsm_client_certificate_public_key": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceHsmClientCertificateCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).RedshiftConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + certIdentifier := d.Get("hsm_client_certificate_identifier").(string) + + input := redshift.CreateHsmClientCertificateInput{ + HsmClientCertificateIdentifier: aws.String(certIdentifier), + } + + input.Tags = Tags(tags.IgnoreAWS()) + + out, err := conn.CreateHsmClientCertificate(&input) + if err != nil { + return fmt.Errorf("error creating Redshift Hsm Client Certificate (%s): %s", certIdentifier, err) + } + + d.SetId(aws.StringValue(out.HsmClientCertificate.HsmClientCertificateIdentifier)) + + return resourceHsmClientCertificateRead(d, meta) +} + +func resourceHsmClientCertificateRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).RedshiftConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + out, err := FindHsmClientCertificateByID(conn, d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Redshift Hsm Client Certificate (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading Redshift Hsm Client Certificate (%s): %w", d.Id(), err) + } + + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: "redshift", + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("hsmclientcertificate:%s", d.Id()), + }.String() + + d.Set("arn", arn) + + d.Set("hsm_client_certificate_identifier", out.HsmClientCertificateIdentifier) + d.Set("hsm_client_certificate_public_key", out.HsmClientCertificatePublicKey) + + tags := KeyValueTags(out.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + + return nil +} + +func resourceHsmClientCertificateUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).RedshiftConn + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating Redshift Hsm Client Certificate (%s) tags: %s", d.Get("arn").(string), err) + } + } + + return resourceHsmClientCertificateRead(d, meta) +} + +func resourceHsmClientCertificateDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).RedshiftConn + + deleteInput := redshift.DeleteHsmClientCertificateInput{ + HsmClientCertificateIdentifier: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting Redshift Hsm Client Certificate: %s", d.Id()) + _, err := conn.DeleteHsmClientCertificate(&deleteInput) + + if err != nil { + if tfawserr.ErrCodeEquals(err, redshift.ErrCodeHsmClientCertificateNotFoundFault) { + return nil + } + return err + } + + return err +} diff --git a/internal/service/redshift/hsm_client_certificate_test.go b/internal/service/redshift/hsm_client_certificate_test.go new file mode 100644 index 00000000000..577bb4dd007 --- /dev/null +++ b/internal/service/redshift/hsm_client_certificate_test.go @@ -0,0 +1,190 @@ +package redshift_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/service/redshift" + 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" + tfredshift "github.com/hashicorp/terraform-provider-aws/internal/service/redshift" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccRedshiftHsmClientCertificate_basic(t *testing.T) { + resourceName := "aws_redshift_hsm_client_certificate.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, redshift.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckHsmClientCertificateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccHsmClientCertificate_Basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckHsmClientCertificateExists(resourceName), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "redshift", regexp.MustCompile(`hsmclientcertificate:.+`)), + resource.TestCheckResourceAttr(resourceName, "hsm_client_certificate_identifier", rName), + resource.TestCheckResourceAttrSet(resourceName, "hsm_client_certificate_public_key"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccRedshiftHsmClientCertificate_tags(t *testing.T) { + resourceName := "aws_redshift_hsm_client_certificate.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, redshift.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckHsmClientCertificateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccHsmClientCertificateConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckHsmClientCertificateExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccHsmClientCertificateConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckHsmClientCertificateExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, { + Config: testAccHsmClientCertificateConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckHsmClientCertificateExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccRedshiftHsmClientCertificate_disappears(t *testing.T) { + resourceName := "aws_redshift_hsm_client_certificate.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, redshift.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckHsmClientCertificateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccHsmClientCertificate_Basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckHsmClientCertificateExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfredshift.ResourceHsmClientCertificate(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckHsmClientCertificateDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).RedshiftConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_redshift_hsm_client_certificate" { + continue + } + + _, err := tfredshift.FindHsmClientCertificateByID(conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Redshift Hsm Client Certificate %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckHsmClientCertificateExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("not found: %s", name) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Snapshot Copy Grant ID (HsmClientCertificateName) is not set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).RedshiftConn + + _, err := tfredshift.FindHsmClientCertificateByID(conn, rs.Primary.ID) + + if err != nil { + return err + } + + return nil + } +} + +func testAccHsmClientCertificate_Basic(rName string) string { + return fmt.Sprintf(` +resource "aws_redshift_hsm_client_certificate" "test" { + hsm_client_certificate_identifier = %[1]q +} +`, rName) +} + +func testAccHsmClientCertificateConfigTags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_redshift_hsm_client_certificate" "test" { + hsm_client_certificate_identifier = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccHsmClientCertificateConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_redshift_hsm_client_certificate" "test" { + hsm_client_certificate_identifier = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/redshift/sweep.go b/internal/service/redshift/sweep.go index f3eaba19dc0..7b9bb3a0756 100644 --- a/internal/service/redshift/sweep.go +++ b/internal/service/redshift/sweep.go @@ -30,6 +30,11 @@ func init() { F: sweepClusters, }) + resource.AddTestSweepers("aws_redshift_hsm_client_certificate", &resource.Sweeper{ + Name: "aws_redshift_hsm_client_certificate", + F: sweepHsmClientCertificates, + }) + resource.AddTestSweepers("aws_redshift_event_subscription", &resource.Sweeper{ Name: "aws_redshift_event_subscription", F: sweepEventSubscriptions, @@ -335,3 +340,48 @@ func sweepSubnetGroups(region string) error { return errs.ErrorOrNil() } + +func sweepHsmClientCertificates(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + + conn := client.(*conns.AWSClient).RedshiftConn + sweepResources := make([]*sweep.SweepResource, 0) + var errs *multierror.Error + + err = conn.DescribeHsmClientCertificatesPages(&redshift.DescribeHsmClientCertificatesInput{}, func(resp *redshift.DescribeHsmClientCertificatesOutput, lastPage bool) bool { + if len(resp.HsmClientCertificates) == 0 { + log.Print("[DEBUG] No Redshift Hsm Client Certificates to sweep") + return !lastPage + } + + for _, c := range resp.HsmClientCertificates { + r := ResourceHsmClientCertificate() + d := r.Data(nil) + d.SetId(aws.StringValue(c.HsmClientCertificateIdentifier)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if err != nil { + errs = multierror.Append(errs, fmt.Errorf("error describing Redshift Hsm Client Certificates: %w", err)) + // in case work can be done, don't jump out yet + } + + if err = sweep.SweepOrchestrator(sweepResources); err != nil { + errs = multierror.Append(errs, fmt.Errorf("error sweeping Redshift Hsm Client Certificates for %s: %w", region, err)) + } + + if sweep.SkipSweepError(errs.ErrorOrNil()) { + log.Printf("[WARN] Skipping Redshift Hsm Client Certificate sweep for %s: %s", region, err) + return nil + } + + return errs.ErrorOrNil() +} diff --git a/website/docs/r/redshift_hsm_client_certificate.html.markdown b/website/docs/r/redshift_hsm_client_certificate.html.markdown new file mode 100644 index 00000000000..dc2a3bd203c --- /dev/null +++ b/website/docs/r/redshift_hsm_client_certificate.html.markdown @@ -0,0 +1,42 @@ +--- +subcategory: "Redshift" +layout: "aws" +page_title: "AWS: aws_redshift_hsm_client_certificate" +description: |- + Creates an HSM client certificate that an Amazon Redshift cluster will use to connect to the client's HSM in order to store and retrieve the keys used to encrypt the cluster databases. +--- + +# Resource: aws_redshift_hsm_client_certificate + +Creates an HSM client certificate that an Amazon Redshift cluster will use to connect to the client's HSM in order to store and retrieve the keys used to encrypt the cluster databases. + +## Example Usage + +```terraform +resource "aws_redshift_hsm_client_certificate" "example" { + hsm_client_certificate_identifier = "example" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `hsm_client_certificate_identifier` - (Required, Forces new resource) The identifier of the HSM client certificate. +* `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - Amazon Resource Name (ARN) of the Hsm Client Certificate. +* `hsm_client_certificate_public_key` - The public key that the Amazon Redshift cluster will use to connect to the HSM. You must register the public key in the HSM. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +Redshift Hsm Client Certificates support import by `hsm_client_certificate_identifier`, e.g., + +```console +$ terraform import aws_redshift_hsm_client_certificate.test example +```