Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SecretsManager Secret Replication #20293

Merged
merged 10 commits into from
Jul 24, 2021
3 changes: 3 additions & 0 deletions .changelog/20293.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_secretsmanager_secret: Add replica support
```
246 changes: 244 additions & 2 deletions aws/resource_aws_secretsmanager_secret.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package aws

import (
"bytes"
"fmt"
"log"
"time"
Expand All @@ -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"
Expand All @@ -37,6 +39,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,
Expand Down Expand Up @@ -73,6 +80,37 @@ func resourceAwsSecretsManagerSecret() *schema.Resource {
validation.IntInSlice([]int{0}),
),
},
"replica": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Set: secretReplicaHash,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"kms_key_id": {
Type: schema.TypeString,
Optional: true,
Computed: 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,
Expand Down Expand Up @@ -122,8 +160,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 {
Expand All @@ -134,6 +173,10 @@ func resourceAwsSecretsManagerSecretCreate(d *schema.ResourceData, meta interfac
input.KmsKeyId = aws.String(v.(string))
}

if v, ok := d.GetOk("replica"); 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
Expand Down Expand Up @@ -267,6 +310,10 @@ func resourceAwsSecretsManagerSecretRead(d *schema.ResourceData, meta interface{
d.Set("kms_key_id", output.KmsKeyId)
d.Set("name", output.Name)

if err := d.Set("replica", flattenSecretsManagerSecretReplicas(output.ReplicationStatus)); err != nil {
return fmt.Errorf("error setting replica: %w", err)
}

pIn := &secretsmanager.GetResourcePolicyInput{
SecretId: aws.String(d.Id()),
}
Expand Down Expand Up @@ -313,6 +360,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)),
Expand Down Expand Up @@ -425,6 +491,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()),
}
Expand All @@ -447,3 +521,171 @@ 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
}

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
}

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())
}
Loading