From f264190439c3cbee4273f875e0bc61b9be9fd950 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Sat, 15 Feb 2025 13:09:52 -0600 Subject: [PATCH 1/6] aws_docdb_cluster: add master_password_wo attribute --- internal/service/docdb/cluster.go | 60 +++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/internal/service/docdb/cluster.go b/internal/service/docdb/cluster.go index 28e241c9953e..f41827f44456 100644 --- a/internal/service/docdb/cluster.go +++ b/internal/service/docdb/cluster.go @@ -15,6 +15,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/docdb" awstypes "github.com/aws/aws-sdk-go-v2/service/docdb/types" "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -57,6 +58,10 @@ func resourceCluster() *schema.Resource { Delete: schema.DefaultTimeout(120 * time.Minute), }, + ValidateRawResourceConfigFuncs: []schema.ValidateRawResourceConfigFunc{ + validation.PreferWriteOnlyAttribute(cty.GetAttrPath("master_password"), cty.GetAttrPath("master_password_wo")), + }, + Schema: map[string]*schema.Schema{ names.AttrAllowMajorVersionUpgrade: { Type: schema.TypeBool, @@ -186,9 +191,21 @@ func resourceCluster() *schema.Resource { ValidateFunc: verify.ValidARN, }, "master_password": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ConflictsWith: []string{"master_password_wo"}, + }, + "master_password_wo": { + Type: schema.TypeString, + Optional: true, + WriteOnly: true, + ConflictsWith: []string{"master_password"}, + }, + "master_password_wo_version": { + Type: schema.TypeInt, + Optional: true, + RequiredWith: []string{"master_password_wo"}, }, "master_username": { Type: schema.TypeString, @@ -332,6 +349,13 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int ApplyImmediately: aws.Bool(true), } + // get write-only value from configuration + masterPasswordWO, di := flex.GetWriteOnlyStringValue(d, cty.GetAttrPath("master_password_wo")) + diags = append(diags, di...) + if diags.HasError() { + return diags + } + if _, ok := d.GetOk("snapshot_identifier"); ok { input := &docdb.RestoreDBClusterFromSnapshotInput{ DBClusterIdentifier: aws.String(identifier), @@ -376,6 +400,11 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int requiresModifyDbCluster = true } + if masterPasswordWO != "" { + inputM.MasterUserPassword = aws.String(masterPasswordWO) + requiresModifyDbCluster = true + } + if v, ok := d.GetOk(names.AttrPort); ok { input.Port = aws.Int32(int32(v.(int))) } @@ -464,8 +493,8 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int } else { // Secondary DocDB clusters part of a global cluster will not supply the master_password if _, ok := d.GetOk("global_cluster_identifier"); !ok { - if _, ok := d.GetOk("master_password"); !ok { - return sdkdiag.AppendErrorf(diags, `provider.aws: aws_docdb_cluster: %s: "master_password": required field is not set`, identifier) + if _, ok := d.GetOk("master_password"); !ok && masterPasswordWO == "" { + return sdkdiag.AppendErrorf(diags, `provider.aws: aws_docdb_cluster: %s: "master_password", "master_password_wo": required field is not set`, identifier) } } @@ -481,7 +510,6 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int DeletionProtection: aws.Bool(d.Get(names.AttrDeletionProtection).(bool)), Engine: aws.String(d.Get(names.AttrEngine).(string)), MasterUsername: aws.String(d.Get("master_username").(string)), - MasterUserPassword: aws.String(d.Get("master_password").(string)), Tags: getTagsIn(ctx), } @@ -517,6 +545,14 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int input.KmsKeyId = aws.String(v.(string)) } + if v, ok := d.GetOk("master_password"); ok { + input.MasterUserPassword = aws.String(v.(string)) + } + + if masterPasswordWO != "" { + input.MasterUserPassword = aws.String(masterPasswordWO) + } + if v, ok := d.GetOk(names.AttrPort); ok { input.Port = aws.Int32(int32(v.(int))) } @@ -673,6 +709,18 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int input.MasterUserPassword = aws.String(d.Get("master_password").(string)) } + if d.HasChange("master_password_wo_version") { + masterPasswordWO, di := flex.GetWriteOnlyStringValue(d, cty.GetAttrPath("master_password_wo")) + diags = append(diags, di...) + if diags.HasError() { + return diags + } + + if masterPasswordWO != "" { + input.MasterUserPassword = aws.String(masterPasswordWO) + } + } + if d.HasChange("preferred_backup_window") { input.PreferredBackupWindow = aws.String(d.Get("preferred_backup_window").(string)) } From ca79b840a447e21c0e71933d0417b3709ed8c36b Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Sat, 15 Feb 2025 13:35:41 -0600 Subject: [PATCH 2/6] aws_docdb_cluster: add test --- internal/service/docdb/cluster_test.go | 52 ++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/internal/service/docdb/cluster_test.go b/internal/service/docdb/cluster_test.go index fb54bd12b4b8..728ca5856313 100644 --- a/internal/service/docdb/cluster_test.go +++ b/internal/service/docdb/cluster_test.go @@ -925,6 +925,34 @@ func TestAccDocDBCluster_storageType(t *testing.T) { }) } +func TestAccDocDBCluster_passwordWriteOnly(t *testing.T) { + ctx := acctest.Context(t) + var dbCluster awstypes.DBCluster + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_docdb_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.DocDBServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_passwordWriteOnly(rName, "avoid-plaintext-passwords", 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(ctx, resourceName, &dbCluster), + ), + }, + { + Config: testAccClusterConfig_passwordWriteOnly(rName, "avoid-plaintext-updated", 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(ctx, resourceName, &dbCluster), + ), + }, + }, + }) +} + func testAccCheckClusterDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).DocDBClient(ctx) @@ -1499,3 +1527,27 @@ resource "aws_docdb_cluster" "test" { } `, rName, storageType)) } + +func testAccClusterConfig_passwordWriteOnly(rName, password string, passworVersion int) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` +resource "aws_docdb_cluster" "test" { + cluster_identifier = %[1]q + + availability_zones = [ + data.aws_availability_zones.available.names[0], + data.aws_availability_zones.available.names[1], + data.aws_availability_zones.available.names[2] + ] + + master_password_wo = %[2]q + master_password_wo_version = %[3]d + master_username = "tfacctest" + skip_final_snapshot = true + + enabled_cloudwatch_logs_exports = [ + "audit", + "profiler", + ] +} +`, rName, password, passworVersion)) +} From 47ea32ad447fd2f5c2ecb55954cca868b728c23e Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Sat, 15 Feb 2025 13:43:48 -0600 Subject: [PATCH 3/6] aws_docdb_cluster: add documentation --- website/docs/r/docdb_cluster.html.markdown | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/website/docs/r/docdb_cluster.html.markdown b/website/docs/r/docdb_cluster.html.markdown index f54c6c9690a4..ab4315a4b629 100644 --- a/website/docs/r/docdb_cluster.html.markdown +++ b/website/docs/r/docdb_cluster.html.markdown @@ -63,8 +63,11 @@ This resource supports the following arguments: made. * `global_cluster_identifier` - (Optional) The global cluster identifier specified on [`aws_docdb_global_cluster`](/docs/providers/aws/r/docdb_global_cluster.html). * `kms_key_id` - (Optional) The ARN for the KMS encryption key. When specifying `kms_key_id`, `storage_encrypted` needs to be set to true. -* `master_password` - (Required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Password for the master DB user. Note that this may - show up in logs, and it will be stored in the state file. Please refer to the DocumentDB Naming Constraints. +* `master_password` - (Optional, required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Password for the master DB user. Note that this may + show up in logs, and it will be stored in the state file. Please refer to the DocumentDB Naming Constraints. Conflicts with `master_password_wo`. +* `master_password_wo` - (Optional, Write-Only, required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Password for the master DB user. Note that this may + show up in logs, and it will be stored in the state file. Please refer to the DocumentDB Naming Constraints. Conflicts with `master_password`. +* `master_password_wo_version` - (Optional) Used together with `master_password_wo` to trigger an update. Increment this value when an update to the `master_password_wo` is required. * `master_username` - (Required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Username for the master DB user. * `port` - (Optional) The port on which the DB accepts connections * `preferred_backup_window` - (Optional) The daily time range during which automated backups are created if automated backups are enabled using the BackupRetentionPeriod parameter.Time in UTC From c0642217e3db6657fbd478ba914ca939b7e1bff2 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Sat, 15 Feb 2025 13:49:44 -0600 Subject: [PATCH 4/6] add CHANGELOG entry --- .changelog/41413.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/41413.txt diff --git a/.changelog/41413.txt b/.changelog/41413.txt new file mode 100644 index 000000000000..1f171642bd6a --- /dev/null +++ b/.changelog/41413.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/ws_docdb_cluster: Add `master_password_wo` write-only attribute +``` \ No newline at end of file From ac61fc172ded8766069bbd08aef5fc67bbebcbe0 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Sat, 15 Feb 2025 14:03:10 -0600 Subject: [PATCH 5/6] aws_docdb_cluster: add test parmeters --- internal/service/docdb/cluster_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/service/docdb/cluster_test.go b/internal/service/docdb/cluster_test.go index 728ca5856313..ed20d05eb623 100644 --- a/internal/service/docdb/cluster_test.go +++ b/internal/service/docdb/cluster_test.go @@ -13,10 +13,12 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/docdb" awstypes "github.com/aws/aws-sdk-go-v2/service/docdb/types" + "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfdocdb "github.com/hashicorp/terraform-provider-aws/internal/service/docdb" @@ -932,8 +934,11 @@ func TestAccDocDBCluster_passwordWriteOnly(t *testing.T) { resourceName := "aws_docdb_cluster.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.DocDBServiceID), + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.DocDBServiceID), + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(version.Must(version.NewVersion("1.11.0"))), + }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckClusterDestroy(ctx), Steps: []resource.TestStep{ From 55f3141c880fe9252c1ec4fd36e064e128ad8d7c Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Tue, 18 Feb 2025 14:40:53 -0600 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Jared Baker --- .changelog/41413.txt | 2 +- website/docs/r/docdb_cluster.html.markdown | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changelog/41413.txt b/.changelog/41413.txt index 1f171642bd6a..64f3c4af067d 100644 --- a/.changelog/41413.txt +++ b/.changelog/41413.txt @@ -1,3 +1,3 @@ ```release-note:enhancement -resource/ws_docdb_cluster: Add `master_password_wo` write-only attribute +resource/aws_docdb_cluster: Add `master_password_wo` write-only attribute ``` \ No newline at end of file diff --git a/website/docs/r/docdb_cluster.html.markdown b/website/docs/r/docdb_cluster.html.markdown index ab4315a4b629..9bb8e65fdfaa 100644 --- a/website/docs/r/docdb_cluster.html.markdown +++ b/website/docs/r/docdb_cluster.html.markdown @@ -66,7 +66,7 @@ This resource supports the following arguments: * `master_password` - (Optional, required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Password for the master DB user. Note that this may show up in logs, and it will be stored in the state file. Please refer to the DocumentDB Naming Constraints. Conflicts with `master_password_wo`. * `master_password_wo` - (Optional, Write-Only, required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Password for the master DB user. Note that this may - show up in logs, and it will be stored in the state file. Please refer to the DocumentDB Naming Constraints. Conflicts with `master_password`. + show up in logs. Please refer to the DocumentDB Naming Constraints. Conflicts with `master_password`. * `master_password_wo_version` - (Optional) Used together with `master_password_wo` to trigger an update. Increment this value when an update to the `master_password_wo` is required. * `master_username` - (Required unless a `snapshot_identifier` or unless a `global_cluster_identifier` is provided when the cluster is the "secondary" cluster of a global database) Username for the master DB user. * `port` - (Optional) The port on which the DB accepts connections