From 0a26219458316413f5b6fd9d972153f373ebe565 Mon Sep 17 00:00:00 2001 From: Tom Withers Date: Fri, 19 Mar 2021 10:47:19 +0000 Subject: [PATCH 01/10] Initial Commit --- aws/resource_aws_secretsmanager_secret.go | 34 ++++++++++++++ ...resource_aws_secretsmanager_secret_test.go | 44 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/aws/resource_aws_secretsmanager_secret.go b/aws/resource_aws_secretsmanager_secret.go index f1cc42e6cb4..7e32a8ff208 100644 --- a/aws/resource_aws_secretsmanager_secret.go +++ b/aws/resource_aws_secretsmanager_secret.go @@ -99,6 +99,32 @@ func resourceAwsSecretsManagerSecret() *schema.Resource { }, }, }, + + "force_overwrite_replica_secret": { + Type: schema.TypeBool, + Optional: true, + }, + + "replica_region": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Required: true, + }, + "kms_key_id": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "tags": tagsSchema(), "tags_all": tagsSchemaComputed(), }, @@ -214,6 +240,14 @@ func resourceAwsSecretsManagerSecretCreate(d *schema.ResourceData, meta interfac } } + if v, ok := d.GetOk("") { + input := &secretsmanager. + + if err != nil { + return fmt.Errorf("error replicatin Secrets Manager Secret") + } + } + return resourceAwsSecretsManagerSecretRead(d, meta) } diff --git a/aws/resource_aws_secretsmanager_secret_test.go b/aws/resource_aws_secretsmanager_secret_test.go index 6bdc63c9cda..3a17931937a 100644 --- a/aws/resource_aws_secretsmanager_secret_test.go +++ b/aws/resource_aws_secretsmanager_secret_test.go @@ -88,6 +88,7 @@ func TestAccAwsSecretsManagerSecret_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "rotation_lambda_arn", ""), resource.TestCheckResourceAttr(resourceName, "rotation_rules.#", "0"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "force_overwrite_replica_secret", "false"), ), }, { @@ -164,6 +165,40 @@ func TestAccAwsSecretsManagerSecret_Description(t *testing.T) { }) } +func TestAccAwsSecretsManagerSecret_OverwriteReplica(t *testing.T) { + var secret secretsmanager.DescribeSecretOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSSecretsManager(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSecretsManagerSecretConfig_OverwriteReplica(rName, "true"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "force_overwrite_replica_secret", "true"), + ), + }, + { + Config: testAccAwsSecretsManagerSecretConfig_OverwriteReplica(rName, "false"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "force_overwrite_replica_secret", "false"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + }, + }, + }) +} + func TestAccAwsSecretsManagerSecret_KmsKeyID(t *testing.T) { var secret secretsmanager.DescribeSecretOutput rName := acctest.RandomWithPrefix("tf-acc-test") @@ -537,6 +572,15 @@ resource "aws_secretsmanager_secret" "test" { `, description, rName) } +func testAccAwsSecretsManagerSecretConfig_OverwriteReplica(rName, force_overwrite_replica_secret string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + force_overwrite_replica_secret = "%s" + name = "%s" +} +`, force_overwrite_replica_secret, rName) +} + func testAccAwsSecretsManagerSecretConfig_Name(rName string) string { return fmt.Sprintf(` resource "aws_secretsmanager_secret" "test" { From fdf6af4e0a27d967fba5502b912d55e4e8c7d11d Mon Sep 17 00:00:00 2001 From: Tom Withers Date: Fri, 19 Mar 2021 14:50:49 +0000 Subject: [PATCH 02/10] First pass at implementation --- aws/resource_aws_secretsmanager_secret.go | 88 ++++++++++++++++++++--- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/aws/resource_aws_secretsmanager_secret.go b/aws/resource_aws_secretsmanager_secret.go index 7e32a8ff208..49fed241a60 100644 --- a/aws/resource_aws_secretsmanager_secret.go +++ b/aws/resource_aws_secretsmanager_secret.go @@ -105,20 +105,33 @@ func resourceAwsSecretsManagerSecret() *schema.Resource { Optional: true, }, - "replica_region": { + "replica_regions": { Type: schema.TypeSet, Optional: true, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "region": { - Type: schema.TypeString, + "force_overwrite_replica_secret": { + Type: schema.TypeBool, Required: true, }, - "kms_key_id": { + "replica_region": { Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Required: true, + }, + "kms_key_id": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, }, }, }, @@ -240,11 +253,48 @@ func resourceAwsSecretsManagerSecretCreate(d *schema.ResourceData, meta interfac } } - if v, ok := d.GetOk("") { - input := &secretsmanager. + if v := d.Get("replication_configuration"); v != nil { + input := secretsmanager.ReplicateSecretToRegionsInput{ + SecretId: aws.String(d.Id()), + } + + if v, ok := d.GetOk("force_overwrite_replica_secret"); ok { + input.ForceOverwriteReplicaSecret = aws.Bool(v.(bool)) + } + + var replicaRegions []*secretsmanager.ReplicaRegionType + if v := d.Get("replica_region"); v != nil { + for _, v := range v.(*schema.Set).List() { + replicaRegionResource := v.(map[string]interface{}) + + replicaRegion := secretsmanager.ReplicaRegionType{} + if v, ok := replicaRegionResource["region"]; ok && v.(string) != "" { + + replicaRegion.Region = aws.String(v.(string)) + } + + if v, ok := d.GetOk("kms_key_id"); ok { + replicaRegion.KmsKeyId = aws.String(v.(string)) + } + + replicaRegions = append(replicaRegions, &replicaRegion) + } + input.AddReplicaRegions = replicaRegions + } + log.Printf("[DEBUG] Enabling Secrets Manager Secret Replication: %s", input) + err := resource.Retry(1*time.Minute, func() *resource.RetryError { + _, err := conn.ReplicateSecretToRegions(&input) + if err != nil { + // AccessDeniedException: Secrets Manager cannot replicate the secret. + if isAWSErr(err, "AccessDeniedException", "") { + return resource.RetryableError(err) + } + } + return nil + }) if err != nil { - return fmt.Errorf("error replicatin Secrets Manager Secret") + return fmt.Errorf("error enabling Secrets Manager Secret %q replication: %w", d.Id(), err) } } @@ -330,10 +380,28 @@ func resourceAwsSecretsManagerSecretRead(d *schema.ResourceData, meta interface{ d.Set("rotation_rules", []interface{}{}) } +<<<<<<< HEAD tags := keyvaluetags.SecretsmanagerKeyValueTags(output.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { +======= + if output.ReplicationStatus != nil && len(output.ReplicationStatus) > 0 { + replicationStatuses := make([]interface{}, len(output.ReplicationStatus)) + for i, rs := range output.ReplicationStatus { + replicationStatus := map[string]interface{}{ + "region": aws.StringValue(rs.Region), + "kms_key_id": aws.StringValue(rs.KmsKeyId), + } + replicationStatuses[i] = replicationStatus + } + if err := d.Set("replica_region", replicationStatuses); err != nil { + return fmt.Errorf("error setting replica_region: %s", err) + } + } + + if err := d.Set("tags", keyvaluetags.SecretsmanagerKeyValueTags(output.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { +>>>>>>> 95dac460e (First pass at implementation) return fmt.Errorf("error setting tags: %w", err) } From d85024c3fb73ee52384360c51f8238dc1a2e430a Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 23 Jul 2021 15:02:30 -0400 Subject: [PATCH 03/10] r/secretsmanager_secret: Add replica attribs --- aws/resource_aws_secretsmanager_secret.go | 241 ++++++++++++---------- 1 file changed, 137 insertions(+), 104 deletions(-) diff --git a/aws/resource_aws_secretsmanager_secret.go b/aws/resource_aws_secretsmanager_secret.go index 49fed241a60..0573dc9abab 100644 --- a/aws/resource_aws_secretsmanager_secret.go +++ b/aws/resource_aws_secretsmanager_secret.go @@ -37,6 +37,11 @@ func resourceAwsSecretsManagerSecret() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "force_overwrite_replica_secret": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, "kms_key_id": { Type: schema.TypeString, Optional: true, @@ -73,6 +78,35 @@ func resourceAwsSecretsManagerSecret() *schema.Resource { validation.IntInSlice([]int{0}), ), }, + "replicas": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kms_key_id": { + Type: schema.TypeString, + Required: true, + }, + "last_accessed_date": { + Type: schema.TypeString, + Computed: true, + }, + "region": { + Type: schema.TypeString, + Required: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "status_message": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, "rotation_enabled": { Deprecated: "Use the aws_secretsmanager_secret_rotation resource instead", Type: schema.TypeBool, @@ -99,45 +133,6 @@ func resourceAwsSecretsManagerSecret() *schema.Resource { }, }, }, - - "force_overwrite_replica_secret": { - Type: schema.TypeBool, - Optional: true, - }, - - "replica_regions": { - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "force_overwrite_replica_secret": { - Type: schema.TypeBool, - Required: true, - }, - "replica_region": { - Type: schema.TypeSet, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "region": { - Type: schema.TypeString, - Required: true, - }, - "kms_key_id": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - }, - }, - }, - }, - }, - }, - "tags": tagsSchema(), "tags_all": tagsSchemaComputed(), }, @@ -161,8 +156,9 @@ func resourceAwsSecretsManagerSecretCreate(d *schema.ResourceData, meta interfac } input := &secretsmanager.CreateSecretInput{ - Description: aws.String(d.Get("description").(string)), - Name: aws.String(secretName), + Description: aws.String(d.Get("description").(string)), + Name: aws.String(secretName), + ForceOverwriteReplicaSecret: aws.Bool(d.Get("force_overwrite_replica_secret").(bool)), } if len(tags) > 0 { @@ -173,6 +169,10 @@ func resourceAwsSecretsManagerSecretCreate(d *schema.ResourceData, meta interfac input.KmsKeyId = aws.String(v.(string)) } + if v, ok := d.GetOk("replicas"); ok && v.(*schema.Set).Len() > 0 { + input.AddReplicaRegions = expandSecretsManagerSecretReplicas(v.(*schema.Set).List()) + } + log.Printf("[DEBUG] Creating Secrets Manager Secret: %s", input) // Retry for secret recreation after deletion @@ -253,51 +253,6 @@ func resourceAwsSecretsManagerSecretCreate(d *schema.ResourceData, meta interfac } } - if v := d.Get("replication_configuration"); v != nil { - input := secretsmanager.ReplicateSecretToRegionsInput{ - SecretId: aws.String(d.Id()), - } - - if v, ok := d.GetOk("force_overwrite_replica_secret"); ok { - input.ForceOverwriteReplicaSecret = aws.Bool(v.(bool)) - } - - var replicaRegions []*secretsmanager.ReplicaRegionType - if v := d.Get("replica_region"); v != nil { - for _, v := range v.(*schema.Set).List() { - replicaRegionResource := v.(map[string]interface{}) - - replicaRegion := secretsmanager.ReplicaRegionType{} - if v, ok := replicaRegionResource["region"]; ok && v.(string) != "" { - - replicaRegion.Region = aws.String(v.(string)) - } - - if v, ok := d.GetOk("kms_key_id"); ok { - replicaRegion.KmsKeyId = aws.String(v.(string)) - } - - replicaRegions = append(replicaRegions, &replicaRegion) - } - input.AddReplicaRegions = replicaRegions - } - - log.Printf("[DEBUG] Enabling Secrets Manager Secret Replication: %s", input) - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - _, err := conn.ReplicateSecretToRegions(&input) - if err != nil { - // AccessDeniedException: Secrets Manager cannot replicate the secret. - if isAWSErr(err, "AccessDeniedException", "") { - return resource.RetryableError(err) - } - } - return nil - }) - if err != nil { - return fmt.Errorf("error enabling Secrets Manager Secret %q replication: %w", d.Id(), err) - } - } - return resourceAwsSecretsManagerSecretRead(d, meta) } @@ -351,6 +306,10 @@ func resourceAwsSecretsManagerSecretRead(d *schema.ResourceData, meta interface{ d.Set("kms_key_id", output.KmsKeyId) d.Set("name", output.Name) + if err := d.Set("replicas", flattenSecretsManagerSecretReplicas(output.ReplicationStatus)); err != nil { + return fmt.Errorf("error setting replicas: %w", err) + } + pIn := &secretsmanager.GetResourcePolicyInput{ SecretId: aws.String(d.Id()), } @@ -380,28 +339,10 @@ func resourceAwsSecretsManagerSecretRead(d *schema.ResourceData, meta interface{ d.Set("rotation_rules", []interface{}{}) } -<<<<<<< HEAD tags := keyvaluetags.SecretsmanagerKeyValueTags(output.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { -======= - if output.ReplicationStatus != nil && len(output.ReplicationStatus) > 0 { - replicationStatuses := make([]interface{}, len(output.ReplicationStatus)) - for i, rs := range output.ReplicationStatus { - replicationStatus := map[string]interface{}{ - "region": aws.StringValue(rs.Region), - "kms_key_id": aws.StringValue(rs.KmsKeyId), - } - replicationStatuses[i] = replicationStatus - } - if err := d.Set("replica_region", replicationStatuses); err != nil { - return fmt.Errorf("error setting replica_region: %s", err) - } - } - - if err := d.Set("tags", keyvaluetags.SecretsmanagerKeyValueTags(output.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { ->>>>>>> 95dac460e (First pass at implementation) return fmt.Errorf("error setting tags: %w", err) } @@ -549,3 +490,95 @@ func resourceAwsSecretsManagerSecretDelete(d *schema.ResourceData, meta interfac return nil } + +func expandSecretsManagerSecretReplica(tfMap map[string]interface{}) *secretsmanager.ReplicaRegionType { + if tfMap == nil { + return nil + } + + apiObject := &secretsmanager.ReplicaRegionType{} + + if v, ok := tfMap["kms_key_id"].(string); ok && v != "" { + apiObject.KmsKeyId = aws.String(v) + } + + if v, ok := tfMap["region"].(string); ok && v != "" { + apiObject.Region = aws.String(v) + } + + return apiObject +} + +func expandSecretsManagerSecretReplicas(tfList []interface{}) []*secretsmanager.ReplicaRegionType { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*secretsmanager.ReplicaRegionType + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandSecretsManagerSecretReplica(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func flattenSecretsManagerSecretReplica(apiObject *secretsmanager.ReplicationStatusType) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.KmsKeyId; v != nil { + tfMap["kms_key_id"] = aws.StringValue(v) + } + + if v := apiObject.LastAccessedDate; v != nil { + tfMap["last_accessed_date"] = aws.TimeValue(v).Format(time.RFC3339) + } + + if v := apiObject.Region; v != nil { + tfMap["region"] = aws.StringValue(v) + } + + if v := apiObject.Status; v != nil { + tfMap["status"] = aws.StringValue(v) + } + + if v := apiObject.StatusMessage; v != nil { + tfMap["status_message"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenSecretsManagerSecretReplicas(apiObjects []*secretsmanager.ReplicationStatusType) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenSecretsManagerSecretReplica(apiObject)) + } + + return tfList +} From 9cdb025430d179a3b9d97cf1566215e3dc690579 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 23 Jul 2021 15:02:54 -0400 Subject: [PATCH 04/10] tests/r/secretsmanager_secret: Add replica attribs --- aws/resource_aws_secretsmanager_secret_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/aws/resource_aws_secretsmanager_secret_test.go b/aws/resource_aws_secretsmanager_secret_test.go index 3a17931937a..ca9ca9b861e 100644 --- a/aws/resource_aws_secretsmanager_secret_test.go +++ b/aws/resource_aws_secretsmanager_secret_test.go @@ -95,7 +95,7 @@ func TestAccAwsSecretsManagerSecret_basic(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + ImportStateVerifyIgnore: []string{"recovery_window_in_days", "force_overwrite_replica_secret"}, }, }, }) @@ -124,7 +124,7 @@ func TestAccAwsSecretsManagerSecret_withNamePrefix(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"recovery_window_in_days", "name_prefix"}, + ImportStateVerifyIgnore: []string{"recovery_window_in_days", "name_prefix", "force_overwrite_replica_secret"}, }, }, }) @@ -159,7 +159,7 @@ func TestAccAwsSecretsManagerSecret_Description(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + ImportStateVerifyIgnore: []string{"recovery_window_in_days", "force_overwrite_replica_secret"}, }, }, }) @@ -193,7 +193,7 @@ func TestAccAwsSecretsManagerSecret_OverwriteReplica(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + ImportStateVerifyIgnore: []string{"recovery_window_in_days", "force_overwrite_replica_secret"}, }, }, }) @@ -228,7 +228,7 @@ func TestAccAwsSecretsManagerSecret_KmsKeyID(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + ImportStateVerifyIgnore: []string{"recovery_window_in_days", "force_overwrite_replica_secret"}, }, }, }) @@ -264,7 +264,7 @@ func TestAccAwsSecretsManagerSecret_RecoveryWindowInDays_Recreate(t *testing.T) ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + ImportStateVerifyIgnore: []string{"recovery_window_in_days", "force_overwrite_replica_secret"}, }, }, }) @@ -309,7 +309,7 @@ func TestAccAwsSecretsManagerSecret_RotationLambdaARN(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + ImportStateVerifyIgnore: []string{"recovery_window_in_days", "force_overwrite_replica_secret"}, }, // Test removing rotation on resource update { @@ -363,7 +363,7 @@ func TestAccAwsSecretsManagerSecret_RotationRules(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + ImportStateVerifyIgnore: []string{"recovery_window_in_days", "force_overwrite_replica_secret"}, }, // Test removing rotation rules on resource update { @@ -425,7 +425,7 @@ func TestAccAwsSecretsManagerSecret_Tags(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + ImportStateVerifyIgnore: []string{"recovery_window_in_days", "force_overwrite_replica_secret"}, }, }, }) From f83fb87ec746acf2480915a522cf6810cacbcab6 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 23 Jul 2021 21:22:59 -0400 Subject: [PATCH 05/10] r/secretsmanager_secret: Add changelog --- .changelog/20293.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/20293.txt diff --git a/.changelog/20293.txt b/.changelog/20293.txt new file mode 100644 index 00000000000..0e6fce12bf4 --- /dev/null +++ b/.changelog/20293.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_secretsmanager_secret: Add replica support +``` \ No newline at end of file From a5689c4665895d9c03940b1e2e9bb237803cd3c1 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 23 Jul 2021 21:23:21 -0400 Subject: [PATCH 06/10] docs/r/secretsmanager_secret: Add replica --- .../r/secretsmanager_secret.html.markdown | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/website/docs/r/secretsmanager_secret.html.markdown b/website/docs/r/secretsmanager_secret.html.markdown index e08788089b3..3b639f82754 100644 --- a/website/docs/r/secretsmanager_secret.html.markdown +++ b/website/docs/r/secretsmanager_secret.html.markdown @@ -43,15 +43,21 @@ resource "aws_secretsmanager_secret" "rotation-example" { The following arguments are supported: -* `name` - (Optional) Specifies the friendly name of the new secret. The secret name can consist of uppercase letters, lowercase letters, digits, and any of the following characters: `/_+=.@-` Conflicts with `name_prefix`. +* `description` - (Optional) Description of the secret. +* `kms_key_id` - (Optional) ARN or Id of the AWS KMS customer master key (CMK) to be used to encrypt the secret values in the versions stored in this secret. If you don't specify this value, then Secrets Manager defaults to using the AWS account's default CMK (the one named `aws/secretsmanager`). If the default KMS CMK with that name doesn't yet exist, then AWS Secrets Manager creates it for you automatically the first time. * `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. Conflicts with `name`. -* `description` - (Optional) A description of the secret. -* `kms_key_id` - (Optional) Specifies the ARN or Id of the AWS KMS customer master key (CMK) to be used to encrypt the secret values in the versions stored in this secret. If you don't specify this value, then Secrets Manager defaults to using the AWS account's default CMK (the one named `aws/secretsmanager`). If the default KMS CMK with that name doesn't yet exist, then AWS Secrets Manager creates it for you automatically the first time. -* `policy` - (Optional) A valid JSON document representing a [resource policy](https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_resource-based-policies.html). For more information about building AWS IAM policy documents with Terraform, see the [AWS IAM Policy Document Guide](https://learn.hashicorp.com/terraform/aws/iam-policy). -* `recovery_window_in_days` - (Optional) Specifies the number of days that AWS Secrets Manager waits before it can delete the secret. This value can be `0` to force deletion without recovery or range from `7` to `30` days. The default value is `30`. -* `rotation_lambda_arn` - (Optional, **DEPRECATED**) Specifies the ARN of the Lambda function that can rotate the secret. Use the `aws_secretsmanager_secret_rotation` resource to manage this configuration instead. As of version 2.67.0, removal of this configuration will no longer remove rotation due to supporting the new resource. Either import the new resource and remove the configuration or manually remove rotation. -* `rotation_rules` - (Optional, **DEPRECATED**) A structure that defines the rotation configuration for this secret. Defined below. Use the `aws_secretsmanager_secret_rotation` resource to manage this configuration instead. As of version 2.67.0, removal of this configuration will no longer remove rotation due to supporting the new resource. Either import the new resource and remove the configuration or manually remove rotation. -* `tags` - (Optional) Specifies a key-value map of user-defined tags that are attached to the secret. 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. +* `name` - (Optional) Friendly name of the new secret. The secret name can consist of uppercase letters, lowercase letters, digits, and any of the following characters: `/_+=.@-` Conflicts with `name_prefix`. +* `policy` - (Optional) Valid JSON document representing a [resource policy](https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_resource-based-policies.html). For more information about building AWS IAM policy documents with Terraform, see the [AWS IAM Policy Document Guide](https://learn.hashicorp.com/terraform/aws/iam-policy). +* `recovery_window_in_days` - (Optional) Number of days that AWS Secrets Manager waits before it can delete the secret. This value can be `0` to force deletion without recovery or range from `7` to `30` days. The default value is `30`. +* `replica` - (Optional) Configuration block to support secret replication. See details below. +* `rotation_lambda_arn` - (Optional, **DEPRECATED**) ARN of the Lambda function that can rotate the secret. Use the `aws_secretsmanager_secret_rotation` resource to manage this configuration instead. As of version 2.67.0, removal of this configuration will no longer remove rotation due to supporting the new resource. Either import the new resource and remove the configuration or manually remove rotation. +* `rotation_rules` - (Optional, **DEPRECATED**) Configuration block for the rotation configuration of this secret. Defined below. Use the `aws_secretsmanager_secret_rotation` resource to manage this configuration instead. As of version 2.67.0, removal of this configuration will no longer remove rotation due to supporting the new resource. Either import the new resource and remove the configuration or manually remove rotation. +* `tags` - (Optional) Key-value map of user-defined tags that are attached to the secret. 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. + +### replica + +* `kms_key_id` - (Optional) ARN, Key ID, or Alias. +* `region` - (Required) Region for replicating the secret. ### rotation_rules @@ -61,10 +67,17 @@ The following arguments are supported: In addition to all arguments above, the following attributes are exported: -* `id` - Amazon Resource Name (ARN) of the secret. -* `arn` - Amazon Resource Name (ARN) of the secret. -* `rotation_enabled` - Specifies whether automatic rotation is enabled for this secret. -* `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). +* `id` - ARN of the secret. +* `arn` - ARN of the secret. +* `rotation_enabled` - Whether automatic rotation is enabled for this secret. +* `replica` - Attributes of a replica are described below. +* `tags_all` - 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). + +### replica + +* `last_accessed_date` - Date that you last accessed the secret in the Region. +* `status` - Status can be `InProgress`, `Failed`, or `InSync`. +* `status_message` - Message such as `Replication succeeded` or `Secret with this name already exists in this region`. ## Import From a77a7e7fa64a6094df73c81e0f5e0bb7d059fc8c Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 23 Jul 2021 21:23:46 -0400 Subject: [PATCH 07/10] test/r/secretsmanager_secret: Add replica --- ...resource_aws_secretsmanager_secret_test.go | 86 +++++++++++++++---- 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/aws/resource_aws_secretsmanager_secret_test.go b/aws/resource_aws_secretsmanager_secret_test.go index ca9ca9b861e..593779670d4 100644 --- a/aws/resource_aws_secretsmanager_secret_test.go +++ b/aws/resource_aws_secretsmanager_secret_test.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/service/secretsmanager" "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/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/secretsmanager/waiter" ) @@ -165,35 +166,37 @@ func TestAccAwsSecretsManagerSecret_Description(t *testing.T) { }) } -func TestAccAwsSecretsManagerSecret_OverwriteReplica(t *testing.T) { +func TestAccAwsSecretsManagerSecret_overwriteReplica(t *testing.T) { + var providers []*schema.Provider var secret secretsmanager.DescribeSecretOutput rName := acctest.RandomWithPrefix("tf-acc-test") resourceName := "aws_secretsmanager_secret.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSSecretsManager(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSSecretsManager(t) }, + ProviderFactories: testAccProviderFactoriesMultipleRegion(&providers, 3), + CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsSecretsManagerSecretConfig_OverwriteReplica(rName, "true"), + Config: testAccAwsSecretsManagerSecretConfig_overwriteReplica(rName, true), Check: resource.ComposeTestCheckFunc( testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), resource.TestCheckResourceAttr(resourceName, "force_overwrite_replica_secret", "true"), ), }, { - Config: testAccAwsSecretsManagerSecretConfig_OverwriteReplica(rName, "false"), + Config: testAccAwsSecretsManagerSecretConfig_overwriteReplicaUpdate(rName, true), Check: resource.ComposeTestCheckFunc( testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), - resource.TestCheckResourceAttr(resourceName, "force_overwrite_replica_secret", "false"), + resource.TestCheckResourceAttr(resourceName, "force_overwrite_replica_secret", "true"), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"recovery_window_in_days", "force_overwrite_replica_secret"}, + Config: testAccAwsSecretsManagerSecretConfig_overwriteReplica(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "force_overwrite_replica_secret", "false"), + ), }, }, }) @@ -572,13 +575,64 @@ resource "aws_secretsmanager_secret" "test" { `, description, rName) } -func testAccAwsSecretsManagerSecretConfig_OverwriteReplica(rName, force_overwrite_replica_secret string) string { - return fmt.Sprintf(` +func testAccAwsSecretsManagerSecretConfig_overwriteReplica(rName string, force_overwrite_replica_secret bool) string { + return composeConfig( + testAccMultipleRegionProviderConfig(3), + fmt.Sprintf(` +resource "aws_kms_key" "test" { + provider = awsalternate + deletion_window_in_days = 7 +} + +resource "aws_kms_key" "test2" { + provider = awsthird + deletion_window_in_days = 7 +} + +data "aws_region" "alternate" { + provider = awsalternate +} + resource "aws_secretsmanager_secret" "test" { - force_overwrite_replica_secret = "%s" - name = "%s" + name = %[1]q + force_overwrite_replica_secret = %[2]t + + replica { + kms_key_id = aws_kms_key.test.key_id + region = data.aws_region.alternate.name + } +} +`, rName, force_overwrite_replica_secret)) +} + +func testAccAwsSecretsManagerSecretConfig_overwriteReplicaUpdate(rName string, force_overwrite_replica_secret bool) string { + return composeConfig( + testAccMultipleRegionProviderConfig(3), + fmt.Sprintf(` +resource "aws_kms_key" "test" { + provider = awsalternate + deletion_window_in_days = 7 +} + +resource "aws_kms_key" "test2" { + provider = awsthird + deletion_window_in_days = 7 +} + +data "aws_region" "third" { + provider = awsthird +} + +resource "aws_secretsmanager_secret" "test" { + name = %[1]q + force_overwrite_replica_secret = %[2]t + + replica { + kms_key_id = aws_kms_key.test2.key_id + region = data.aws_region.third.name + } } -`, force_overwrite_replica_secret, rName) +`, rName, force_overwrite_replica_secret)) } func testAccAwsSecretsManagerSecretConfig_Name(rName string) string { From 474a312c644aa571cae23aab4ec6ad529c91921c Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 23 Jul 2021 21:24:08 -0400 Subject: [PATCH 08/10] r/secretsmanager_secret: Add replica --- aws/resource_aws_secretsmanager_secret.go | 118 +++++++++++++++++++++- 1 file changed, 113 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_secretsmanager_secret.go b/aws/resource_aws_secretsmanager_secret.go index 0573dc9abab..0a71ae7cbe3 100644 --- a/aws/resource_aws_secretsmanager_secret.go +++ b/aws/resource_aws_secretsmanager_secret.go @@ -1,6 +1,7 @@ package aws import ( + "bytes" "fmt" "log" "time" @@ -12,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" iamwaiter "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter" "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/secretsmanager/waiter" @@ -78,15 +80,17 @@ func resourceAwsSecretsManagerSecret() *schema.Resource { validation.IntInSlice([]int{0}), ), }, - "replicas": { + "replica": { Type: schema.TypeSet, Optional: true, Computed: true, + Set: secretReplicaHash, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "kms_key_id": { Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, }, "last_accessed_date": { Type: schema.TypeString, @@ -95,6 +99,7 @@ func resourceAwsSecretsManagerSecret() *schema.Resource { "region": { Type: schema.TypeString, Required: true, + Computed: true, }, "status": { Type: schema.TypeString, @@ -169,7 +174,7 @@ func resourceAwsSecretsManagerSecretCreate(d *schema.ResourceData, meta interfac input.KmsKeyId = aws.String(v.(string)) } - if v, ok := d.GetOk("replicas"); ok && v.(*schema.Set).Len() > 0 { + if v, ok := d.GetOk("replica"); ok && v.(*schema.Set).Len() > 0 { input.AddReplicaRegions = expandSecretsManagerSecretReplicas(v.(*schema.Set).List()) } @@ -306,8 +311,8 @@ func resourceAwsSecretsManagerSecretRead(d *schema.ResourceData, meta interface{ d.Set("kms_key_id", output.KmsKeyId) d.Set("name", output.Name) - if err := d.Set("replicas", flattenSecretsManagerSecretReplicas(output.ReplicationStatus)); err != nil { - return fmt.Errorf("error setting replicas: %w", err) + if err := d.Set("replica", flattenSecretsManagerSecretReplicas(output.ReplicationStatus)); err != nil { + return fmt.Errorf("error setting replica: %w", err) } pIn := &secretsmanager.GetResourcePolicyInput{ @@ -356,6 +361,25 @@ func resourceAwsSecretsManagerSecretRead(d *schema.ResourceData, meta interface{ func resourceAwsSecretsManagerSecretUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).secretsmanagerconn + if d.HasChange("replica") { + o, n := d.GetChange("replica") + + os := o.(*schema.Set) + ns := n.(*schema.Set) + + err := removeSecretsManagerSecretReplicas(conn, d.Id(), os.Difference(ns).List()) + + if err != nil { + return fmt.Errorf("error deleting Secrets Manager Secret replica: %w", err) + } + + err = addSecretsManagerSecretReplicas(conn, d.Id(), d.Get("force_overwrite_replica_secret").(bool), ns.Difference(os).List()) + + if err != nil { + return fmt.Errorf("error adding Secrets Manager Secret replica: %w", err) + } + } + if d.HasChanges("description", "kms_key_id") { input := &secretsmanager.UpdateSecretInput{ Description: aws.String(d.Get("description").(string)), @@ -468,6 +492,14 @@ func resourceAwsSecretsManagerSecretUpdate(d *schema.ResourceData, meta interfac func resourceAwsSecretsManagerSecretDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).secretsmanagerconn + if v, ok := d.GetOk("replica"); ok && v.(*schema.Set).Len() > 0 { + err := removeSecretsManagerSecretReplicas(conn, d.Id(), v.(*schema.Set).List()) + + if err != nil { + return fmt.Errorf("error deleting Secrets Manager Secret replica: %w", err) + } + } + input := &secretsmanager.DeleteSecretInput{ SecretId: aws.String(d.Id()), } @@ -491,6 +523,66 @@ func resourceAwsSecretsManagerSecretDelete(d *schema.ResourceData, meta interfac return nil } +func removeSecretsManagerSecretReplicas(conn *secretsmanager.SecretsManager, id string, tfList []interface{}) error { + if len(tfList) == 0 { + return nil + } + + input := &secretsmanager.RemoveRegionsFromReplicationInput{ + SecretId: aws.String(id), + } + + var regions []string + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + regions = append(regions, tfMap["region"].(string)) + } + + input.RemoveReplicaRegions = aws.StringSlice(regions) + + log.Printf("[DEBUG] Removing Secrets Manager Secret Replicas: %s", input) + + _, err := conn.RemoveRegionsFromReplication(input) + + if err != nil { + if isAWSErr(err, secretsmanager.ErrCodeResourceNotFoundException, "") { + return nil + } + + return err + } + + return nil +} + +func addSecretsManagerSecretReplicas(conn *secretsmanager.SecretsManager, id string, forceOverwrite bool, tfList []interface{}) error { + if len(tfList) == 0 { + return nil + } + + input := &secretsmanager.ReplicateSecretToRegionsInput{ + SecretId: aws.String(id), + ForceOverwriteReplicaSecret: aws.Bool(forceOverwrite), + AddReplicaRegions: expandSecretsManagerSecretReplicas(tfList), + } + + log.Printf("[DEBUG] Removing Secrets Manager Secret Replica: %s", input) + + _, err := conn.ReplicateSecretToRegions(input) + + if err != nil { + return err + } + + return nil +} + func expandSecretsManagerSecretReplica(tfMap map[string]interface{}) *secretsmanager.ReplicaRegionType { if tfMap == nil { return nil @@ -582,3 +674,19 @@ func flattenSecretsManagerSecretReplicas(apiObjects []*secretsmanager.Replicatio return tfList } + +func secretReplicaHash(v interface{}) int { + var buf bytes.Buffer + + m := v.(map[string]interface{}) + + if v, ok := m["kms_key_id"].(string); ok { + buf.WriteString(fmt.Sprintf("%s-", v)) + } + + if v, ok := m["region"].(string); ok { + buf.WriteString(fmt.Sprintf("%s-", v)) + } + + return hashcode.String(buf.String()) +} From 33dd59a31eb3a7468257468696ed32cd58c6c97b Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 23 Jul 2021 21:28:37 -0400 Subject: [PATCH 09/10] r/secretsmanager_secret: Fix validation --- aws/resource_aws_secretsmanager_secret.go | 1 - 1 file changed, 1 deletion(-) diff --git a/aws/resource_aws_secretsmanager_secret.go b/aws/resource_aws_secretsmanager_secret.go index 0a71ae7cbe3..8158086b9d4 100644 --- a/aws/resource_aws_secretsmanager_secret.go +++ b/aws/resource_aws_secretsmanager_secret.go @@ -99,7 +99,6 @@ func resourceAwsSecretsManagerSecret() *schema.Resource { "region": { Type: schema.TypeString, Required: true, - Computed: true, }, "status": { Type: schema.TypeString, From 2d08aa14a77f2a6f4c71ec69c2d45646a491faad Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Fri, 23 Jul 2021 22:38:27 -0400 Subject: [PATCH 10/10] tests/r/secretsmanager_secret: Add basic replica test --- ...resource_aws_secretsmanager_secret_test.go | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_secretsmanager_secret_test.go b/aws/resource_aws_secretsmanager_secret_test.go index 593779670d4..9aee10460f6 100644 --- a/aws/resource_aws_secretsmanager_secret_test.go +++ b/aws/resource_aws_secretsmanager_secret_test.go @@ -166,6 +166,30 @@ func TestAccAwsSecretsManagerSecret_Description(t *testing.T) { }) } +func TestAccAwsSecretsManagerSecret_basicReplica(t *testing.T) { + var providers []*schema.Provider + var secret secretsmanager.DescribeSecretOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSSecretsManager(t); testAccMultipleRegionPreCheck(t, 2) }, + ErrorCheck: testAccErrorCheck(t, secretsmanager.EndpointsID), + ProviderFactories: testAccProviderFactoriesMultipleRegion(&providers, 2), + CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSecretsManagerSecretConfig_basicReplica(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "force_overwrite_replica_secret", "false"), + resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), + ), + }, + }, + }) +} + func TestAccAwsSecretsManagerSecret_overwriteReplica(t *testing.T) { var providers []*schema.Provider var secret secretsmanager.DescribeSecretOutput @@ -173,7 +197,8 @@ func TestAccAwsSecretsManagerSecret_overwriteReplica(t *testing.T) { resourceName := "aws_secretsmanager_secret.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSSecretsManager(t) }, + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSSecretsManager(t); testAccMultipleRegionPreCheck(t, 3) }, + ErrorCheck: testAccErrorCheck(t, secretsmanager.EndpointsID), ProviderFactories: testAccProviderFactoriesMultipleRegion(&providers, 3), CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, Steps: []resource.TestStep{ @@ -575,6 +600,24 @@ resource "aws_secretsmanager_secret" "test" { `, description, rName) } +func testAccAwsSecretsManagerSecretConfig_basicReplica(rName string) string { + return composeConfig( + testAccMultipleRegionProviderConfig(2), + fmt.Sprintf(` +data "aws_region" "alternate" { + provider = awsalternate +} + +resource "aws_secretsmanager_secret" "test" { + name = %[1]q + + replica { + region = data.aws_region.alternate.name + } +} +`, rName)) +} + func testAccAwsSecretsManagerSecretConfig_overwriteReplica(rName string, force_overwrite_replica_secret bool) string { return composeConfig( testAccMultipleRegionProviderConfig(3),