From 22d33024ee1b808f2c360fa48dcbc86fa6e4b53d Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Thu, 19 Apr 2018 16:46:40 -0400 Subject: [PATCH 1/3] New Resources and Data Sources: aws_secretsmanager_secret* --- aws/data_source_aws_secretsmanager_secret.go | 116 ++++ ...a_source_aws_secretsmanager_secret_test.go | 155 +++++ ...ource_aws_secretsmanager_secret_version.go | 87 +++ ..._aws_secretsmanager_secret_version_test.go | 164 +++++ aws/provider.go | 4 + aws/resource_aws_secretsmanager_secret.go | 316 ++++++++++ ...resource_aws_secretsmanager_secret_test.go | 585 ++++++++++++++++++ ...ource_aws_secretsmanager_secret_version.go | 205 ++++++ ..._aws_secretsmanager_secret_version_test.go | 233 +++++++ aws/tagsSecretsManager.go | 74 +++ aws/tagsSecretsManager_test.go | 79 +++ website/aws.erb | 16 + .../d/secretsmanager_secret.html.markdown | 45 ++ ...ecretsmanager_secret_version.html.markdown | 44 ++ .../r/secretsmanager_secret.html.markdown | 68 ++ ...ecretsmanager_secret_version.html.markdown | 45 ++ 16 files changed, 2236 insertions(+) create mode 100644 aws/data_source_aws_secretsmanager_secret.go create mode 100644 aws/data_source_aws_secretsmanager_secret_test.go create mode 100644 aws/data_source_aws_secretsmanager_secret_version.go create mode 100644 aws/data_source_aws_secretsmanager_secret_version_test.go create mode 100644 aws/resource_aws_secretsmanager_secret.go create mode 100644 aws/resource_aws_secretsmanager_secret_test.go create mode 100644 aws/resource_aws_secretsmanager_secret_version.go create mode 100644 aws/resource_aws_secretsmanager_secret_version_test.go create mode 100644 aws/tagsSecretsManager.go create mode 100644 aws/tagsSecretsManager_test.go create mode 100644 website/docs/d/secretsmanager_secret.html.markdown create mode 100644 website/docs/d/secretsmanager_secret_version.html.markdown create mode 100644 website/docs/r/secretsmanager_secret.html.markdown create mode 100644 website/docs/r/secretsmanager_secret_version.html.markdown diff --git a/aws/data_source_aws_secretsmanager_secret.go b/aws/data_source_aws_secretsmanager_secret.go new file mode 100644 index 00000000000..b076b8d1cfd --- /dev/null +++ b/aws/data_source_aws_secretsmanager_secret.go @@ -0,0 +1,116 @@ +package aws + +import ( + "errors" + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsSecretsManagerSecret() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsSecretsManagerSecretRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validateArn, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "kms_key_id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "rotation_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "rotation_lambda_arn": { + Type: schema.TypeString, + Computed: true, + }, + "rotation_rules": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "automatically_after_days": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "tags": { + Type: schema.TypeMap, + Computed: true, + }, + }, + } +} + +func dataSourceAwsSecretsManagerSecretRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).secretsmanagerconn + var secretID string + if v, ok := d.GetOk("arn"); ok { + secretID = v.(string) + } + if v, ok := d.GetOk("name"); ok { + if secretID != "" { + return errors.New("specify only arn or name") + } + secretID = v.(string) + } + + if secretID == "" { + return errors.New("must specify either arn or name") + } + + input := &secretsmanager.DescribeSecretInput{ + SecretId: aws.String(secretID), + } + + log.Printf("[DEBUG] Reading Secrets Manager Secret: %s", input) + output, err := conn.DescribeSecret(input) + if err != nil { + if isAWSErr(err, secretsmanager.ErrCodeResourceNotFoundException, "") { + return fmt.Errorf("Secrets Manager Secret %q not found", secretID) + } + return fmt.Errorf("error reading Secrets Manager Secret: %s", err) + } + + if output.ARN == nil { + return fmt.Errorf("Secrets Manager Secret %q not found", secretID) + } + + d.SetId(aws.StringValue(output.ARN)) + d.Set("arn", output.ARN) + d.Set("description", output.Description) + d.Set("kms_key_id", output.KmsKeyId) + d.Set("name", output.Name) + d.Set("rotation_enabled", output.RotationEnabled) + d.Set("rotation_lambda_arn", output.RotationLambdaARN) + + if err := d.Set("rotation_rules", flattenSecretsManagerRotationRules(output.RotationRules)); err != nil { + return fmt.Errorf("error setting rotation_rules: %s", err) + } + + if err := d.Set("tags", tagsToMapSecretsManager(output.Tags)); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + return nil +} diff --git a/aws/data_source_aws_secretsmanager_secret_test.go b/aws/data_source_aws_secretsmanager_secret_test.go new file mode 100644 index 00000000000..7a0b24a5647 --- /dev/null +++ b/aws/data_source_aws_secretsmanager_secret_test.go @@ -0,0 +1,155 @@ +package aws + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceAwsSecretsManagerSecret_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsSecretsManagerSecretConfig_MissingRequired, + ExpectError: regexp.MustCompile(`must specify either arn or name`), + }, + { + Config: testAccDataSourceAwsSecretsManagerSecretConfig_MultipleSpecified, + ExpectError: regexp.MustCompile(`specify only arn or name`), + }, + { + Config: testAccDataSourceAwsSecretsManagerSecretConfig_NonExistent, + ExpectError: regexp.MustCompile(`not found`), + }, + }, + }) +} + +func TestAccDataSourceAwsSecretsManagerSecret_ARN(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret.test" + datasourceName := "data.aws_secretsmanager_secret.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsSecretsManagerSecretConfig_ARN(rName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceAwsSecretsManagerSecretCheck(datasourceName, resourceName), + ), + }, + }, + }) +} + +func TestAccDataSourceAwsSecretsManagerSecret_Name(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret.test" + datasourceName := "data.aws_secretsmanager_secret.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsSecretsManagerSecretConfig_Name(rName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceAwsSecretsManagerSecretCheck(datasourceName, resourceName), + ), + }, + }, + }) +} + +func testAccDataSourceAwsSecretsManagerSecretCheck(datasourceName, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + resource, ok := s.RootModule().Resources[datasourceName] + if !ok { + return fmt.Errorf("root module has no resource called %s", datasourceName) + } + + dataSource, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("root module has no resource called %s", resourceName) + } + + attrNames := []string{ + "arn", + "description", + "kms_key_id", + "name", + "rotation_enabled", + "rotation_lambda_arn", + "rotation_rules.#", + "tags.#", + } + + for _, attrName := range attrNames { + if resource.Primary.Attributes[attrName] != dataSource.Primary.Attributes[attrName] { + return fmt.Errorf( + "%s is %s; want %s", + attrName, + resource.Primary.Attributes[attrName], + dataSource.Primary.Attributes[attrName], + ) + } + } + + return nil + } +} + +func testAccDataSourceAwsSecretsManagerSecretConfig_ARN(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "wrong" { + name = "%[1]s-wrong" +} +resource "aws_secretsmanager_secret" "test" { + name = "%[1]s" +} + +data "aws_secretsmanager_secret" "test" { + arn = "${aws_secretsmanager_secret.test.arn}" +} +`, rName) +} + +const testAccDataSourceAwsSecretsManagerSecretConfig_MissingRequired = ` +data "aws_secretsmanager_secret" "test" {} +` + +const testAccDataSourceAwsSecretsManagerSecretConfig_MultipleSpecified = ` +data "aws_secretsmanager_secret" "test" { + arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:tf-acc-test-does-not-exist" + name = "tf-acc-test-does-not-exist" +} +` + +func testAccDataSourceAwsSecretsManagerSecretConfig_Name(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "wrong" { + name = "%[1]s-wrong" +} +resource "aws_secretsmanager_secret" "test" { + name = "%[1]s" +} + +data "aws_secretsmanager_secret" "test" { + name = "${aws_secretsmanager_secret.test.name}" +} +`, rName) +} + +const testAccDataSourceAwsSecretsManagerSecretConfig_NonExistent = ` +data "aws_secretsmanager_secret" "test" { + name = "tf-acc-test-does-not-exist" +} +` diff --git a/aws/data_source_aws_secretsmanager_secret_version.go b/aws/data_source_aws_secretsmanager_secret_version.go new file mode 100644 index 00000000000..2f1ae276667 --- /dev/null +++ b/aws/data_source_aws_secretsmanager_secret_version.go @@ -0,0 +1,87 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsSecretsManagerSecretVersion() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsSecretsManagerSecretVersionRead, + + Schema: map[string]*schema.Schema{ + "secret_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "secret_string": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + "version_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "version_stage": { + Type: schema.TypeString, + Optional: true, + Default: "AWSCURRENT", + }, + "version_stages": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceAwsSecretsManagerSecretVersionRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).secretsmanagerconn + secretID := d.Get("secret_id").(string) + var version string + + input := &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(secretID), + } + + if v, ok := d.GetOk("version_id"); ok { + versionID := v.(string) + input.VersionId = aws.String(versionID) + version = versionID + } else { + versionStage := d.Get("version_stage").(string) + input.VersionStage = aws.String(versionStage) + version = versionStage + } + + log.Printf("[DEBUG] Reading Secrets Manager Secret Version: %s", input) + output, err := conn.GetSecretValue(input) + if err != nil { + if isAWSErr(err, secretsmanager.ErrCodeResourceNotFoundException, "") { + return fmt.Errorf("Secrets Manager Secret %q Version %q not found", secretID, version) + } + if isAWSErr(err, secretsmanager.ErrCodeInvalidRequestException, "You can’t perform this operation on the secret because it was deleted") { + return fmt.Errorf("Secrets Manager Secret %q Version %q not found", secretID, version) + } + return fmt.Errorf("error reading Secrets Manager Secret Version: %s", err) + } + + d.SetId(fmt.Sprintf("%s|%s", secretID, version)) + d.Set("secret_id", secretID) + d.Set("secret_string", output.SecretString) + d.Set("version_id", output.VersionId) + + if err := d.Set("version_stages", flattenStringList(output.VersionStages)); err != nil { + return fmt.Errorf("error setting version_stages: %s", err) + } + + return nil +} diff --git a/aws/data_source_aws_secretsmanager_secret_version_test.go b/aws/data_source_aws_secretsmanager_secret_version_test.go new file mode 100644 index 00000000000..f650e359829 --- /dev/null +++ b/aws/data_source_aws_secretsmanager_secret_version_test.go @@ -0,0 +1,164 @@ +package aws + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceAwsSecretsManagerSecretVersion_Basic(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret_version.test" + datasourceName := "data.aws_secretsmanager_secret_version.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsSecretsManagerSecretVersionConfig_NonExistent, + ExpectError: regexp.MustCompile(`not found`), + }, + { + Config: testAccDataSourceAwsSecretsManagerSecretVersionConfig_VersionStage_Default(rName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceAwsSecretsManagerSecretVersionCheck(datasourceName, resourceName), + ), + }, + }, + }) +} + +func TestAccDataSourceAwsSecretsManagerSecretVersion_VersionID(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret_version.test" + datasourceName := "data.aws_secretsmanager_secret_version.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsSecretsManagerSecretVersionConfig_VersionID(rName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceAwsSecretsManagerSecretVersionCheck(datasourceName, resourceName), + ), + }, + }, + }) +} + +func TestAccDataSourceAwsSecretsManagerSecretVersion_VersionStage(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret_version.test" + datasourceName := "data.aws_secretsmanager_secret_version.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsSecretsManagerSecretVersionConfig_VersionStage_Custom(rName), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceAwsSecretsManagerSecretVersionCheck(datasourceName, resourceName), + ), + }, + }, + }) +} + +func testAccDataSourceAwsSecretsManagerSecretVersionCheck(datasourceName, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + resource, ok := s.RootModule().Resources[datasourceName] + if !ok { + return fmt.Errorf("root module has no resource called %s", datasourceName) + } + + dataSource, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("root module has no resource called %s", resourceName) + } + + attrNames := []string{ + "secret_value", + "version_stages.#", + } + + for _, attrName := range attrNames { + if resource.Primary.Attributes[attrName] != dataSource.Primary.Attributes[attrName] { + return fmt.Errorf( + "%s is %s; want %s", + attrName, + resource.Primary.Attributes[attrName], + dataSource.Primary.Attributes[attrName], + ) + } + } + + return nil + } +} + +const testAccDataSourceAwsSecretsManagerSecretVersionConfig_NonExistent = ` +data "aws_secretsmanager_secret_version" "test" { + secret_id = "tf-acc-test-does-not-exist" +} +` + +func testAccDataSourceAwsSecretsManagerSecretVersionConfig_VersionID(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%[1]s" +} + +resource "aws_secretsmanager_secret_version" "test" { + secret_id = "${aws_secretsmanager_secret.test.id}" + secret_string = "test-string" +} + +data "aws_secretsmanager_secret_version" "test" { + secret_id = "${aws_secretsmanager_secret.test.id}" + version_id = "${aws_secretsmanager_secret_version.test.version_id}" +} +`, rName) +} + +func testAccDataSourceAwsSecretsManagerSecretVersionConfig_VersionStage_Custom(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%[1]s" +} + +resource "aws_secretsmanager_secret_version" "test" { + secret_id = "${aws_secretsmanager_secret.test.id}" + secret_string = "test-string" + version_stages = ["test-stage", "AWSCURRENT"] +} + +data "aws_secretsmanager_secret_version" "test" { + secret_id = "${aws_secretsmanager_secret_version.test.secret_id}" + version_stage = "test-stage" +} +`, rName) +} + +func testAccDataSourceAwsSecretsManagerSecretVersionConfig_VersionStage_Default(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%[1]s" +} + +resource "aws_secretsmanager_secret_version" "test" { + secret_id = "${aws_secretsmanager_secret.test.id}" + secret_string = "test-string" +} + +data "aws_secretsmanager_secret_version" "test" { + secret_id = "${aws_secretsmanager_secret_version.test.secret_id}" +} +`, rName) +} diff --git a/aws/provider.go b/aws/provider.go index 851e1d7a3ae..7f0c3aa01ba 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -223,6 +223,8 @@ func Provider() terraform.ResourceProvider { "aws_route53_zone": dataSourceAwsRoute53Zone(), "aws_s3_bucket": dataSourceAwsS3Bucket(), "aws_s3_bucket_object": dataSourceAwsS3BucketObject(), + "aws_secretsmanager_secret": dataSourceAwsSecretsManagerSecret(), + "aws_secretsmanager_secret_version": dataSourceAwsSecretsManagerSecretVersion(), "aws_sns_topic": dataSourceAwsSnsTopic(), "aws_sqs_queue": dataSourceAwsSqsQueue(), "aws_ssm_parameter": dataSourceAwsSsmParameter(), @@ -489,6 +491,8 @@ func Provider() terraform.ResourceProvider { "aws_route_table": resourceAwsRouteTable(), "aws_default_route_table": resourceAwsDefaultRouteTable(), "aws_route_table_association": resourceAwsRouteTableAssociation(), + "aws_secretsmanager_secret": resourceAwsSecretsManagerSecret(), + "aws_secretsmanager_secret_version": resourceAwsSecretsManagerSecretVersion(), "aws_ses_active_receipt_rule_set": resourceAwsSesActiveReceiptRuleSet(), "aws_ses_domain_identity": resourceAwsSesDomainIdentity(), "aws_ses_domain_identity_verification": resourceAwsSesDomainIdentityVerification(), diff --git a/aws/resource_aws_secretsmanager_secret.go b/aws/resource_aws_secretsmanager_secret.go new file mode 100644 index 00000000000..22e1598aede --- /dev/null +++ b/aws/resource_aws_secretsmanager_secret.go @@ -0,0 +1,316 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" +) + +func resourceAwsSecretsManagerSecret() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSecretsManagerSecretCreate, + Read: resourceAwsSecretsManagerSecretRead, + Update: resourceAwsSecretsManagerSecretUpdate, + Delete: resourceAwsSecretsManagerSecretDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "kms_key_id": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "recovery_window_in_days": { + Type: schema.TypeInt, + Optional: true, + Default: 30, + ValidateFunc: validation.IntBetween(7, 30), + }, + "rotation_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "rotation_lambda_arn": { + Type: schema.TypeString, + Optional: true, + }, + "rotation_rules": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "automatically_after_days": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + "tags": { + Type: schema.TypeMap, + Optional: true, + }, + }, + } +} + +func resourceAwsSecretsManagerSecretCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).secretsmanagerconn + + input := &secretsmanager.CreateSecretInput{ + Description: aws.String(d.Get("description").(string)), + Name: aws.String(d.Get("name").(string)), + } + + if v, ok := d.GetOk("kms_key_id"); ok && v.(string) != "" { + input.KmsKeyId = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating Secrets Manager Secret: %s", input) + output, err := conn.CreateSecret(input) + if err != nil { + return fmt.Errorf("error creating Secrets Manager Secret: %s", err) + } + + d.SetId(aws.StringValue(output.ARN)) + + if v, ok := d.GetOk("rotation_lambda_arn"); ok && v.(string) != "" { + input := &secretsmanager.RotateSecretInput{ + RotationLambdaARN: aws.String(v.(string)), + RotationRules: expandSecretsManagerRotationRules(d.Get("rotation_rules").([]interface{})), + SecretId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Enabling Secrets Manager Secret rotation: %s", input) + err := resource.Retry(1*time.Minute, func() *resource.RetryError { + _, err := conn.RotateSecret(input) + if err != nil { + // AccessDeniedException: Secrets Manager cannot invoke the specified Lambda function. + if isAWSErr(err, "AccessDeniedException", "") { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + if err != nil { + return fmt.Errorf("error enabling Secrets Manager Secret %q rotation: %s", d.Id(), err) + } + } + + if v, ok := d.GetOk("tags"); ok { + input := &secretsmanager.TagResourceInput{ + SecretId: aws.String(d.Id()), + Tags: tagsFromMapSecretsManager(v.(map[string]interface{})), + } + + log.Printf("[DEBUG] Tagging Secrets Manager Secret: %s", input) + _, err := conn.TagResource(input) + if err != nil { + return fmt.Errorf("error tagging Secrets Manager Secret %q: %s", d.Id(), input) + } + } + + return resourceAwsSecretsManagerSecretRead(d, meta) +} + +func resourceAwsSecretsManagerSecretRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).secretsmanagerconn + + input := &secretsmanager.DescribeSecretInput{ + SecretId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Reading Secrets Manager Secret: %s", input) + output, err := conn.DescribeSecret(input) + if err != nil { + if isAWSErr(err, secretsmanager.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] Secrets Manager Secret %q not found - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("error reading Secrets Manager Secret: %s", err) + } + + d.Set("arn", output.ARN) + d.Set("description", output.Description) + d.Set("kms_key_id", output.KmsKeyId) + d.Set("name", output.Name) + d.Set("rotation_enabled", output.RotationEnabled) + + if aws.BoolValue(output.RotationEnabled) { + d.Set("rotation_lambda_arn", output.RotationLambdaARN) + if err := d.Set("rotation_rules", flattenSecretsManagerRotationRules(output.RotationRules)); err != nil { + return fmt.Errorf("error setting rotation_rules: %s", err) + } + } else { + d.Set("rotation_lambda_arn", "") + d.Set("rotation_rules", []interface{}{}) + } + + if err := d.Set("tags", tagsToMapSecretsManager(output.Tags)); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + return nil +} + +func resourceAwsSecretsManagerSecretUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).secretsmanagerconn + + if d.HasChange("description") || d.HasChange("kms_key_id") { + input := &secretsmanager.UpdateSecretInput{ + Description: aws.String(d.Get("description").(string)), + SecretId: aws.String(d.Id()), + } + + if v, ok := d.GetOk("kms_key_id"); ok && v.(string) != "" { + input.KmsKeyId = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Updating Secrets Manager Secret: %s", input) + _, err := conn.UpdateSecret(input) + if err != nil { + return fmt.Errorf("error updating Secrets Manager Secret: %s", err) + } + } + + if d.HasChange("rotation_lambda_arn") || d.HasChange("rotation_rules") { + if v, ok := d.GetOk("rotation_lambda_arn"); ok && v.(string) != "" { + input := &secretsmanager.RotateSecretInput{ + RotationLambdaARN: aws.String(v.(string)), + RotationRules: expandSecretsManagerRotationRules(d.Get("rotation_rules").([]interface{})), + SecretId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Enabling Secrets Manager Secret rotation: %s", input) + err := resource.Retry(1*time.Minute, func() *resource.RetryError { + _, err := conn.RotateSecret(input) + if err != nil { + // AccessDeniedException: Secrets Manager cannot invoke the specified Lambda function. + if isAWSErr(err, "AccessDeniedException", "") { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + if err != nil { + return fmt.Errorf("error updating Secrets Manager Secret %q rotation: %s", d.Id(), err) + } + } else { + input := &secretsmanager.CancelRotateSecretInput{ + SecretId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Cancelling Secrets Manager Secret rotation: %s", input) + _, err := conn.CancelRotateSecret(input) + if err != nil { + return fmt.Errorf("error cancelling Secret Manager Secret %q rotation: %s", d.Id(), err) + } + } + } + + if d.HasChange("tags") { + oraw, nraw := d.GetChange("tags") + o := oraw.(map[string]interface{}) + n := nraw.(map[string]interface{}) + create, remove := diffTagsSecretsManager(tagsFromMapSecretsManager(o), tagsFromMapSecretsManager(n)) + + if len(remove) > 0 { + log.Printf("[DEBUG] Removing Secrets Manager Secret %q tags: %#v", d.Id(), remove) + k := make([]*string, len(remove), len(remove)) + for i, t := range remove { + k[i] = t.Key + } + + _, err := conn.UntagResource(&secretsmanager.UntagResourceInput{ + SecretId: aws.String(d.Id()), + TagKeys: k, + }) + if err != nil { + return fmt.Errorf("error updating Secrets Manager Secrets %q tags: %s", d.Id(), err) + } + } + if len(create) > 0 { + log.Printf("[DEBUG] Creating Secrets Manager Secret %q tags: %#v", d.Id(), create) + _, err := conn.TagResource(&secretsmanager.TagResourceInput{ + SecretId: aws.String(d.Id()), + Tags: create, + }) + if err != nil { + return fmt.Errorf("error updating Secrets Manager Secrets %q tags: %s", d.Id(), err) + } + } + } + + return resourceAwsSecretsManagerSecretRead(d, meta) +} + +func resourceAwsSecretsManagerSecretDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).secretsmanagerconn + + input := &secretsmanager.DeleteSecretInput{ + RecoveryWindowInDays: aws.Int64(int64(d.Get("recovery_window_in_days").(int))), + SecretId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting Secrets Manager Secret: %s", input) + _, err := conn.DeleteSecret(input) + if err != nil { + if isAWSErr(err, secretsmanager.ErrCodeResourceNotFoundException, "") { + return nil + } + return fmt.Errorf("error deleting Secrets Manager Secret: %s", err) + } + + return nil +} + +func expandSecretsManagerRotationRules(l []interface{}) *secretsmanager.RotationRulesType { + if len(l) == 0 { + return nil + } + + m := l[0].(map[string]interface{}) + + rules := &secretsmanager.RotationRulesType{ + AutomaticallyAfterDays: aws.Int64(int64(m["automatically_after_days"].(int))), + } + + return rules +} + +func flattenSecretsManagerRotationRules(rules *secretsmanager.RotationRulesType) []interface{} { + if rules == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "automatically_after_days": int(aws.Int64Value(rules.AutomaticallyAfterDays)), + } + + return []interface{}{m} +} diff --git a/aws/resource_aws_secretsmanager_secret_test.go b/aws/resource_aws_secretsmanager_secret_test.go new file mode 100644 index 00000000000..0f37593686d --- /dev/null +++ b/aws/resource_aws_secretsmanager_secret_test.go @@ -0,0 +1,585 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func init() { + resource.AddTestSweepers("aws_secretsmanager_secret", &resource.Sweeper{ + Name: "aws_secretsmanager_secret", + F: testSweepSecretsManagerSecrets, + }) +} + +func testSweepSecretsManagerSecrets(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*AWSClient).secretsmanagerconn + + return conn.ListSecretsPages(&secretsmanager.ListSecretsInput{}, func(page *secretsmanager.ListSecretsOutput, isLast bool) bool { + if len(page.SecretList) == 0 { + log.Print("[DEBUG] No Secrets Manager Secrets to sweep") + return true + } + + for _, secret := range page.SecretList { + name := aws.StringValue(secret.Name) + if !strings.HasPrefix(name, "tf-acc-test-") { + log.Printf("[INFO] Skipping Secrets Manager Secret: %s", name) + continue + } + log.Printf("[INFO] Deleting Secrets Manager Secret: %s", name) + input := &secretsmanager.DeleteSecretInput{ + RecoveryWindowInDays: aws.Int64(7), + SecretId: aws.String(name), + } + + _, err := conn.DeleteSecret(input) + if err != nil { + if isAWSErr(err, secretsmanager.ErrCodeResourceNotFoundException, "") { + continue + } + log.Printf("[ERROR] Failed to delete Secrets Manager Secret (%s): %s", name, err) + } + } + + return !isLast + }) +} + +func TestAccAwsSecretsManagerSecret_Basic(t *testing.T) { + var secret secretsmanager.DescribeSecretOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSecretsManagerSecretConfig_Name(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestMatchResourceAttr(resourceName, "arn", regexp.MustCompile(fmt.Sprintf("^arn:[^:]+:secretsmanager:[^:]+:[^:]+:secret:%s-.+$", rName))), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "kms_key_id", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "recovery_window_in_days", "30"), + resource.TestCheckResourceAttr(resourceName, "rotation_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "rotation_lambda_arn", ""), + resource.TestCheckResourceAttr(resourceName, "rotation_rules.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + }, + }, + }) +} + +func TestAccAwsSecretsManagerSecret_Description(t *testing.T) { + var secret secretsmanager.DescribeSecretOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSecretsManagerSecretConfig_Description(rName, "description1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "description", "description1"), + ), + }, + { + Config: testAccAwsSecretsManagerSecretConfig_Description(rName, "description2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "description", "description2"), + ), + }, + { + 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") + resourceName := "aws_secretsmanager_secret.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSecretsManagerSecretConfig_KmsKeyID(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttrSet(resourceName, "kms_key_id"), + ), + }, + { + Config: testAccAwsSecretsManagerSecretConfig_KmsKeyID_Updated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttrSet(resourceName, "kms_key_id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + }, + }, + }) +} + +func TestAccAwsSecretsManagerSecret_RotationLambdaARN(t *testing.T) { + var secret secretsmanager.DescribeSecretOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, + Steps: []resource.TestStep{ + // Test enabling rotation on resource creation + { + Config: testAccAwsSecretsManagerSecretConfig_RotationLambdaARN(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "rotation_enabled", "true"), + resource.TestMatchResourceAttr(resourceName, "rotation_lambda_arn", regexp.MustCompile(fmt.Sprintf("^arn:[^:]+:lambda:[^:]+:[^:]+:function:%s-1$", rName))), + ), + }, + // Test updating rotation + // We need a valid rotation function for this testing + // InvalidRequestException: A previous rotation isn’t complete. That rotation will be reattempted. + /* + { + Config: testAccAwsSecretsManagerSecretConfig_RotationLambdaARN_Updated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "rotation_enabled", "true"), + resource.TestMatchResourceAttr(resourceName, "rotation_lambda_arn", regexp.MustCompile(fmt.Sprintf("^arn:[^:]+:lambda:[^:]+:[^:]+:function:%s-2$", rName))), + ), + }, + */ + // Test importing rotation + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + }, + // Test removing rotation on resource update + { + Config: testAccAwsSecretsManagerSecretConfig_Name(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "rotation_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "rotation_lambda_arn", ""), + ), + }, + }, + }) +} + +func TestAccAwsSecretsManagerSecret_RotationRules(t *testing.T) { + var secret secretsmanager.DescribeSecretOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, + Steps: []resource.TestStep{ + // Test creating rotation rules on resource creation + { + Config: testAccAwsSecretsManagerSecretConfig_RotationRules(rName, 7), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "rotation_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "rotation_rules.#", "1"), + resource.TestCheckResourceAttr(resourceName, "rotation_rules.0.automatically_after_days", "7"), + ), + }, + // Test updating rotation rules + // We need a valid rotation function for this testing + // InvalidRequestException: A previous rotation isn’t complete. That rotation will be reattempted. + /* + { + Config: testAccAwsSecretsManagerSecretConfig_RotationRules(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "rotation_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "rotation_rules.#", "1"), + resource.TestCheckResourceAttr(resourceName, "rotation_rules.0.automatically_after_days", "1"), + ), + }, + */ + // Test importing rotation rules + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + }, + // Test removing rotation rules on resource update + { + Config: testAccAwsSecretsManagerSecretConfig_Name(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "rotation_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "rotation_rules.#", "0"), + ), + }, + }, + }) +} + +func TestAccAwsSecretsManagerSecret_Tags(t *testing.T) { + var secret secretsmanager.DescribeSecretOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsSecretsManagerSecretDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSecretsManagerSecretConfig_Tags_Single(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1value"), + ), + }, + { + Config: testAccAwsSecretsManagerSecretConfig_Tags_SingleUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1value-updated"), + ), + }, + { + Config: testAccAwsSecretsManagerSecretConfig_Tags_Multiple(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1value"), + resource.TestCheckResourceAttr(resourceName, "tags.tag2", "tag2value"), + ), + }, + { + Config: testAccAwsSecretsManagerSecretConfig_Tags_Single(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretExists(resourceName, &secret), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.tag1", "tag1value"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"recovery_window_in_days"}, + }, + }, + }) +} + +func testAccCheckAwsSecretsManagerSecretDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).secretsmanagerconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_secretsmanager_secret" { + continue + } + + input := &secretsmanager.DescribeSecretInput{ + SecretId: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeSecret(input) + + if err != nil { + if isAWSErr(err, secretsmanager.ErrCodeResourceNotFoundException, "") { + return nil + } + return err + } + + if output != nil && output.DeletedDate == nil { + return fmt.Errorf("Secret %q still exists", rs.Primary.ID) + } + } + + return nil + +} + +func testAccCheckAwsSecretsManagerSecretExists(resourceName string, secret *secretsmanager.DescribeSecretOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := testAccProvider.Meta().(*AWSClient).secretsmanagerconn + input := &secretsmanager.DescribeSecretInput{ + SecretId: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeSecret(input) + + if err != nil { + return err + } + + if output == nil { + return fmt.Errorf("Secret %q does not exist", rs.Primary.ID) + } + + *secret = *output + + return nil + } +} + +func testAccAwsSecretsManagerSecretConfig_Description(rName, description string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + description = "%s" + name = "%s" +} +`, description, rName) +} + +func testAccAwsSecretsManagerSecretConfig_Name(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%s" +} +`, rName) +} + +func testAccAwsSecretsManagerSecretConfig_KmsKeyID(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "test1" { + deletion_window_in_days = 7 +} + +resource "aws_kms_key" "test2" { + deletion_window_in_days = 7 +} + +resource "aws_secretsmanager_secret" "test" { + kms_key_id = "${aws_kms_key.test1.id}" + name = "%s" +} +`, rName) +} + +func testAccAwsSecretsManagerSecretConfig_KmsKeyID_Updated(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "test1" { + deletion_window_in_days = 7 +} + +resource "aws_kms_key" "test2" { + deletion_window_in_days = 7 +} + +resource "aws_secretsmanager_secret" "test" { + kms_key_id = "${aws_kms_key.test2.id}" + name = "%s" +} +`, rName) +} + +func testAccAwsSecretsManagerSecretConfig_RotationLambdaARN(rName string) string { + return baseAccAWSLambdaConfig(rName, rName, rName) + fmt.Sprintf(` +# Not a real rotation function +resource "aws_lambda_function" "test1" { + filename = "test-fixtures/lambdatest.zip" + function_name = "%[1]s-1" + handler = "exports.example" + role = "${aws_iam_role.iam_for_lambda.arn}" + runtime = "nodejs4.3" +} + +resource "aws_lambda_permission" "test1" { + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.test1.function_name}" + principal = "secretsmanager.amazonaws.com" + statement_id = "AllowExecutionFromSecretsManager1" +} + +# Not a real rotation function +resource "aws_lambda_function" "test2" { + filename = "test-fixtures/lambdatest.zip" + function_name = "%[1]s-2" + handler = "exports.example" + role = "${aws_iam_role.iam_for_lambda.arn}" + runtime = "nodejs4.3" +} + +resource "aws_lambda_permission" "test2" { + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.test2.function_name}" + principal = "secretsmanager.amazonaws.com" + statement_id = "AllowExecutionFromSecretsManager2" +} + +resource "aws_secretsmanager_secret" "test" { + name = "%[1]s" + rotation_lambda_arn = "${aws_lambda_function.test1.arn}" + + depends_on = ["aws_lambda_permission.test1"] +} +`, rName) +} + +func testAccAwsSecretsManagerSecretConfig_RotationLambdaARN_Updated(rName string) string { + return baseAccAWSLambdaConfig(rName, rName, rName) + fmt.Sprintf(` +# Not a real rotation function +resource "aws_lambda_function" "test1" { + filename = "test-fixtures/lambdatest.zip" + function_name = "%[1]s-1" + handler = "exports.example" + role = "${aws_iam_role.iam_for_lambda.arn}" + runtime = "nodejs4.3" +} + +resource "aws_lambda_permission" "test1" { + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.test1.function_name}" + principal = "secretsmanager.amazonaws.com" + statement_id = "AllowExecutionFromSecretsManager1" +} + +# Not a real rotation function +resource "aws_lambda_function" "test2" { + filename = "test-fixtures/lambdatest.zip" + function_name = "%[1]s-2" + handler = "exports.example" + role = "${aws_iam_role.iam_for_lambda.arn}" + runtime = "nodejs4.3" +} + +resource "aws_lambda_permission" "test2" { + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.test2.function_name}" + principal = "secretsmanager.amazonaws.com" + statement_id = "AllowExecutionFromSecretsManager2" +} + +resource "aws_secretsmanager_secret" "test" { + name = "%[1]s" + rotation_lambda_arn = "${aws_lambda_function.test2.arn}" + + depends_on = ["aws_lambda_permission.test2"] +} +`, rName) +} + +func testAccAwsSecretsManagerSecretConfig_RotationRules(rName string, automaticallyAfterDays int) string { + return baseAccAWSLambdaConfig(rName, rName, rName) + fmt.Sprintf(` +# Not a real rotation function +resource "aws_lambda_function" "test" { + filename = "test-fixtures/lambdatest.zip" + function_name = "%[1]s" + handler = "exports.example" + role = "${aws_iam_role.iam_for_lambda.arn}" + runtime = "nodejs4.3" +} + +resource "aws_lambda_permission" "test" { + action = "lambda:InvokeFunction" + function_name = "${aws_lambda_function.test.function_name}" + principal = "secretsmanager.amazonaws.com" + statement_id = "AllowExecutionFromSecretsManager1" +} + +resource "aws_secretsmanager_secret" "test" { + name = "%[1]s" + rotation_lambda_arn = "${aws_lambda_function.test.arn}" + + rotation_rules { + automatically_after_days = %[2]d + } + + depends_on = ["aws_lambda_permission.test"] +} +`, rName, automaticallyAfterDays) +} + +func testAccAwsSecretsManagerSecretConfig_Tags_Single(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%s" + + tags = { + tag1 = "tag1value" + } +} +`, rName) +} + +func testAccAwsSecretsManagerSecretConfig_Tags_SingleUpdated(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%s" + + tags = { + tag1 = "tag1value-updated" + } +} +`, rName) +} + +func testAccAwsSecretsManagerSecretConfig_Tags_Multiple(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%s" + + tags = { + tag1 = "tag1value" + tag2 = "tag2value" + } +} +`, rName) +} diff --git a/aws/resource_aws_secretsmanager_secret_version.go b/aws/resource_aws_secretsmanager_secret_version.go new file mode 100644 index 00000000000..a65065f0338 --- /dev/null +++ b/aws/resource_aws_secretsmanager_secret_version.go @@ -0,0 +1,205 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsSecretsManagerSecretVersion() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSecretsManagerSecretVersionCreate, + Read: resourceAwsSecretsManagerSecretVersionRead, + Update: resourceAwsSecretsManagerSecretVersionUpdate, + Delete: resourceAwsSecretsManagerSecretVersionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "secret_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "secret_string": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Sensitive: true, + }, + "version_id": { + Type: schema.TypeString, + Computed: true, + }, + "version_stages": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func resourceAwsSecretsManagerSecretVersionCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).secretsmanagerconn + secretID := d.Get("secret_id").(string) + + input := &secretsmanager.PutSecretValueInput{ + SecretId: aws.String(secretID), + SecretString: aws.String(d.Get("secret_string").(string)), + } + + if v, ok := d.GetOk("version_stages"); ok { + input.VersionStages = expandStringList(v.(*schema.Set).List()) + } + + log.Printf("[DEBUG] Putting Secrets Manager Secret %q value", secretID) + output, err := conn.PutSecretValue(input) + if err != nil { + return fmt.Errorf("error putting Secrets Manager Secret value: %s", err) + } + + d.SetId(fmt.Sprintf("%s|%s", secretID, aws.StringValue(output.VersionId))) + + return resourceAwsSecretsManagerSecretVersionRead(d, meta) +} + +func resourceAwsSecretsManagerSecretVersionRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).secretsmanagerconn + + secretID, versionID, err := decodeSecretsManagerSecretVersionID(d.Id()) + if err != nil { + return err + } + + input := &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(secretID), + VersionId: aws.String(versionID), + } + + log.Printf("[DEBUG] Reading Secrets Manager Secret Version: %s", input) + output, err := conn.GetSecretValue(input) + if err != nil { + if isAWSErr(err, secretsmanager.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] Secrets Manager Secret Version %q not found - removing from state", d.Id()) + d.SetId("") + return nil + } + if isAWSErr(err, secretsmanager.ErrCodeInvalidRequestException, "You can’t perform this operation on the secret because it was deleted") { + log.Printf("[WARN] Secrets Manager Secret Version %q not found - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("error reading Secrets Manager Secret Version: %s", err) + } + + d.Set("secret_id", secretID) + d.Set("secret_string", output.SecretString) + d.Set("version_id", output.VersionId) + + if err := d.Set("version_stages", flattenStringList(output.VersionStages)); err != nil { + return fmt.Errorf("error setting version_stages: %s", err) + } + + return nil +} + +func resourceAwsSecretsManagerSecretVersionUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).secretsmanagerconn + + secretID, versionID, err := decodeSecretsManagerSecretVersionID(d.Id()) + if err != nil { + return err + } + + o, n := d.GetChange("version_stages") + os := o.(*schema.Set) + ns := n.(*schema.Set) + stagesToAdd := ns.Difference(os).List() + stagesToRemove := os.Difference(ns).List() + + for _, stage := range stagesToAdd { + input := &secretsmanager.UpdateSecretVersionStageInput{ + MoveToVersionId: aws.String(versionID), + SecretId: aws.String(secretID), + VersionStage: aws.String(stage.(string)), + } + + log.Printf("[DEBUG] Updating Secrets Manager Secret Version Stage: %s", input) + _, err := conn.UpdateSecretVersionStage(input) + if err != nil { + return fmt.Errorf("error updating Secrets Manager Secret %q Version Stage %q: %s", secretID, stage.(string), err) + } + } + + for _, stage := range stagesToRemove { + // InvalidParameterException: You can only move staging label AWSCURRENT to a different secret version. It can’t be completely removed. + if stage.(string) == "AWSCURRENT" { + log.Printf("[INFO] Skipping removal of AWSCURRENT staging label for secret %q version %q", secretID, versionID) + continue + } + input := &secretsmanager.UpdateSecretVersionStageInput{ + RemoveFromVersionId: aws.String(versionID), + SecretId: aws.String(secretID), + VersionStage: aws.String(stage.(string)), + } + log.Printf("[DEBUG] Updating Secrets Manager Secret Version Stage: %s", input) + _, err := conn.UpdateSecretVersionStage(input) + if err != nil { + return fmt.Errorf("error updating Secrets Manager Secret %q Version Stage %q: %s", secretID, stage.(string), err) + } + } + + return resourceAwsSecretsManagerSecretVersionRead(d, meta) +} + +func resourceAwsSecretsManagerSecretVersionDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).secretsmanagerconn + + secretID, versionID, err := decodeSecretsManagerSecretVersionID(d.Id()) + if err != nil { + return err + } + + if v, ok := d.GetOk("version_stages"); ok { + for _, stage := range v.(*schema.Set).List() { + // InvalidParameterException: You can only move staging label AWSCURRENT to a different secret version. It can’t be completely removed. + if stage.(string) == "AWSCURRENT" { + log.Printf("[WARN] Cannot remove AWSCURRENT staging label, which may leave the secret %q version %q active", secretID, versionID) + continue + } + input := &secretsmanager.UpdateSecretVersionStageInput{ + RemoveFromVersionId: aws.String(versionID), + SecretId: aws.String(secretID), + VersionStage: aws.String(stage.(string)), + } + log.Printf("[DEBUG] Updating Secrets Manager Secret Version Stage: %s", input) + _, err := conn.UpdateSecretVersionStage(input) + if err != nil { + if isAWSErr(err, secretsmanager.ErrCodeResourceNotFoundException, "") { + return nil + } + if isAWSErr(err, secretsmanager.ErrCodeInvalidRequestException, "You can’t perform this operation on the secret because it was deleted") { + return nil + } + return fmt.Errorf("error updating Secrets Manager Secret %q Version Stage %q: %s", secretID, stage.(string), err) + } + } + } + + return nil +} + +func decodeSecretsManagerSecretVersionID(id string) (string, string, error) { + idParts := strings.Split(id, "|") + if len(idParts) != 2 { + return "", "", fmt.Errorf("expected ID in format SecretID|VersionID, received: %s", id) + } + return idParts[0], idParts[1], nil +} diff --git a/aws/resource_aws_secretsmanager_secret_version_test.go b/aws/resource_aws_secretsmanager_secret_version_test.go new file mode 100644 index 00000000000..adbcfec3360 --- /dev/null +++ b/aws/resource_aws_secretsmanager_secret_version_test.go @@ -0,0 +1,233 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAwsSecretsManagerSecretVersion_Basic(t *testing.T) { + var version secretsmanager.GetSecretValueOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret_version.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsSecretsManagerSecretVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSecretsManagerSecretVersionConfig_SecretString(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretVersionExists(resourceName, &version), + resource.TestCheckResourceAttr(resourceName, "secret_string", "test-string"), + resource.TestCheckResourceAttrSet(resourceName, "version_id"), + resource.TestCheckResourceAttr(resourceName, "version_stages.#", "1"), + resource.TestCheckResourceAttr(resourceName, "version_stages.3070137", "AWSCURRENT"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsSecretsManagerSecretVersion_VersionStages(t *testing.T) { + var version secretsmanager.GetSecretValueOutput + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_secretsmanager_secret_version.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsSecretsManagerSecretVersionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsSecretsManagerSecretVersionConfig_VersionStages_Single(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretVersionExists(resourceName, &version), + resource.TestCheckResourceAttr(resourceName, "secret_string", "test-string"), + resource.TestCheckResourceAttr(resourceName, "version_stages.#", "2"), + resource.TestCheckResourceAttr(resourceName, "version_stages.3070137", "AWSCURRENT"), + resource.TestCheckResourceAttr(resourceName, "version_stages.2848565413", "one"), + ), + }, + { + Config: testAccAwsSecretsManagerSecretVersionConfig_VersionStages_SingleUpdated(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretVersionExists(resourceName, &version), + resource.TestCheckResourceAttr(resourceName, "secret_string", "test-string"), + resource.TestCheckResourceAttr(resourceName, "version_stages.#", "2"), + resource.TestCheckResourceAttr(resourceName, "version_stages.3070137", "AWSCURRENT"), + resource.TestCheckResourceAttr(resourceName, "version_stages.3351840846", "two"), + ), + }, + { + Config: testAccAwsSecretsManagerSecretVersionConfig_VersionStages_Multiple(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsSecretsManagerSecretVersionExists(resourceName, &version), + resource.TestCheckResourceAttr(resourceName, "secret_string", "test-string"), + resource.TestCheckResourceAttr(resourceName, "version_stages.#", "3"), + resource.TestCheckResourceAttr(resourceName, "version_stages.3070137", "AWSCURRENT"), + resource.TestCheckResourceAttr(resourceName, "version_stages.2848565413", "one"), + resource.TestCheckResourceAttr(resourceName, "version_stages.3351840846", "two"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAwsSecretsManagerSecretVersionDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).secretsmanagerconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_secretsmanager_secret_version" { + continue + } + + secretID, versionID, err := decodeSecretsManagerSecretVersionID(rs.Primary.ID) + if err != nil { + return err + } + + input := &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(secretID), + VersionId: aws.String(versionID), + } + + output, err := conn.GetSecretValue(input) + + if err != nil { + if isAWSErr(err, secretsmanager.ErrCodeResourceNotFoundException, "") { + return nil + } + if isAWSErr(err, secretsmanager.ErrCodeInvalidRequestException, "You can’t perform this operation on the secret because it was deleted") { + return nil + } + return err + } + + if output == nil { + return nil + } + + if len(output.VersionStages) == 0 { + return nil + } + + if len(output.VersionStages) == 1 && aws.StringValue(output.VersionStages[0]) == "AWSCURRENT" { + return nil + } + + return fmt.Errorf("Secret Version %q still exists", rs.Primary.ID) + } + + return nil + +} + +func testAccCheckAwsSecretsManagerSecretVersionExists(resourceName string, version *secretsmanager.GetSecretValueOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + secretID, versionID, err := decodeSecretsManagerSecretVersionID(rs.Primary.ID) + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*AWSClient).secretsmanagerconn + + input := &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(secretID), + VersionId: aws.String(versionID), + } + + output, err := conn.GetSecretValue(input) + + if err != nil { + return err + } + + if output == nil { + return fmt.Errorf("Secret Version %q does not exist", rs.Primary.ID) + } + + *version = *output + + return nil + } +} + +func testAccAwsSecretsManagerSecretVersionConfig_SecretString(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%s" +} + +resource "aws_secretsmanager_secret_version" "test" { + secret_id = "${aws_secretsmanager_secret.test.id}" + secret_string = "test-string" +} +`, rName) +} + +func testAccAwsSecretsManagerSecretVersionConfig_VersionStages_Single(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%s" +} + +resource "aws_secretsmanager_secret_version" "test" { + secret_id = "${aws_secretsmanager_secret.test.id}" + secret_string = "test-string" + + version_stages = ["one", "AWSCURRENT"] +} +`, rName) +} + +func testAccAwsSecretsManagerSecretVersionConfig_VersionStages_SingleUpdated(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%s" +} + +resource "aws_secretsmanager_secret_version" "test" { + secret_id = "${aws_secretsmanager_secret.test.id}" + secret_string = "test-string" + + version_stages = ["two", "AWSCURRENT"] +} +`, rName) +} + +func testAccAwsSecretsManagerSecretVersionConfig_VersionStages_Multiple(rName string) string { + return fmt.Sprintf(` +resource "aws_secretsmanager_secret" "test" { + name = "%s" +} + +resource "aws_secretsmanager_secret_version" "test" { + secret_id = "${aws_secretsmanager_secret.test.id}" + secret_string = "test-string" + + version_stages = ["one", "two", "AWSCURRENT"] +} +`, rName) +} diff --git a/aws/tagsSecretsManager.go b/aws/tagsSecretsManager.go new file mode 100644 index 00000000000..55921831797 --- /dev/null +++ b/aws/tagsSecretsManager.go @@ -0,0 +1,74 @@ +package aws + +import ( + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/secretsmanager" +) + +// diffTags takes our tags locally and the ones remotely and returns +// the set of tags that must be created, and the set of tags that must +// be destroyed. +func diffTagsSecretsManager(oldTags, newTags []*secretsmanager.Tag) ([]*secretsmanager.Tag, []*secretsmanager.Tag) { + // First, we're creating everything we have + create := make(map[string]interface{}) + for _, t := range newTags { + create[*t.Key] = *t.Value + } + + // Build the list of what to remove + var remove []*secretsmanager.Tag + for _, t := range oldTags { + old, ok := create[*t.Key] + if !ok || old != *t.Value { + // Delete it! + remove = append(remove, t) + } + } + + return tagsFromMapSecretsManager(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapSecretsManager(m map[string]interface{}) []*secretsmanager.Tag { + result := make([]*secretsmanager.Tag, 0, len(m)) + for k, v := range m { + t := &secretsmanager.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + } + if !tagIgnoredSecretsManager(t) { + result = append(result, t) + } + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapSecretsManager(ts []*secretsmanager.Tag) map[string]string { + result := make(map[string]string) + for _, t := range ts { + if !tagIgnoredSecretsManager(t) { + result[*t.Key] = *t.Value + } + } + + return result +} + +// compare a tag against a list of strings and checks if it should +// be ignored or not +func tagIgnoredSecretsManager(t *secretsmanager.Tag) bool { + filter := []string{"^aws:"} + for _, v := range filter { + log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) + if r, _ := regexp.MatchString(v, *t.Key); r == true { + log.Printf("[DEBUG] Found AWS specific tag %s (val: %s), ignoring.\n", *t.Key, *t.Value) + return true + } + } + return false +} diff --git a/aws/tagsSecretsManager_test.go b/aws/tagsSecretsManager_test.go new file mode 100644 index 00000000000..d6eebe5343c --- /dev/null +++ b/aws/tagsSecretsManager_test.go @@ -0,0 +1,79 @@ +package aws + +import ( + "reflect" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/secretsmanager" +) + +// go test -v -run="TestDiffSecretsManagerTags" +func TestDiffSecretsManagerTags(t *testing.T) { + cases := []struct { + Old, New map[string]interface{} + Create, Remove map[string]string + }{ + // Basic add/remove + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "bar": "baz", + }, + Create: map[string]string{ + "bar": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + + // Modify + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "foo": "baz", + }, + Create: map[string]string{ + "foo": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + } + + for i, tc := range cases { + c, r := diffTagsSecretsManager(tagsFromMapSecretsManager(tc.Old), tagsFromMapSecretsManager(tc.New)) + cm := tagsToMapSecretsManager(c) + rm := tagsToMapSecretsManager(r) + if !reflect.DeepEqual(cm, tc.Create) { + t.Fatalf("%d: bad create: %#v", i, cm) + } + if !reflect.DeepEqual(rm, tc.Remove) { + t.Fatalf("%d: bad remove: %#v", i, rm) + } + } +} + +// go test -v -run="TestIgnoringTagsSecretsManager" +func TestIgnoringTagsSecretsManager(t *testing.T) { + var ignoredTags []*secretsmanager.Tag + ignoredTags = append(ignoredTags, &secretsmanager.Tag{ + Key: aws.String("aws:cloudformation:logical-id"), + Value: aws.String("foo"), + }) + ignoredTags = append(ignoredTags, &secretsmanager.Tag{ + Key: aws.String("aws:foo:bar"), + Value: aws.String("baz"), + }) + for _, tag := range ignoredTags { + if !tagIgnoredSecretsManager(tag) { + t.Fatalf("Tag %v with value %v not ignored, but should be!", *tag.Key, *tag.Value) + } + } +} diff --git a/website/aws.erb b/website/aws.erb index 8f1f078d61c..74751f5c011 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -232,6 +232,12 @@ > aws_s3_bucket_object + > + aws_secretsmanager_secret + + > + aws_secretsmanager_secret_version + > aws_security_group @@ -1669,6 +1675,16 @@ + > + Secrets Manager Resources + + > SES Resources diff --git a/website/docs/d/secretsmanager_secret.html.markdown b/website/docs/d/secretsmanager_secret.html.markdown new file mode 100644 index 00000000000..ec8e23a3b57 --- /dev/null +++ b/website/docs/d/secretsmanager_secret.html.markdown @@ -0,0 +1,45 @@ +--- +layout: "aws" +page_title: "AWS: aws_secretsmanager_secret" +sidebar_current: "docs-aws-datasource-secretsmanager-secret" +description: |- + Retrieve metadata information about a Secrets Manager secret +--- + +# Data Source: aws_secretsmanager_secret + +Retrieve metadata information about a Secrets Manager secret. To retrieve a secret value, see the [`aws_secretsmanager_secret_version` data source](/docs/providers/aws/d/secretsmanager_secret_version.html). + +## Example Usage + +### ARN + +```hcl +data "aws_secretsmanager_secret" "by-arn" { + arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:example-123456" +} +``` + +### Name + +```hcl +data "aws_secretsmanager_secret" "by-name" { + name = "example" +} +``` + +## Argument Reference + +* `arn` - (Optional) The Amazon Resource Name (ARN) of the secret to retrieve. +* `name` - (Optional) The name of the secret to retrieve. + +## Attributes Reference + +* `arn` - The Amazon Resource Name (ARN) of the secret. +* `description` - A description of the secret. +* `kms_key_id` - The Key Management Service (KMS) Customer Master Key (CMK) associated with the secret. +* `id` - The Amazon Resource Name (ARN) of the secret. +* `rotation_enabled` - Whether rotation is enabled or not. +* `rotation_lambda_arn` - Rotation Lambda function Amazon Resource Name (ARN) if rotation is enabled. +* `rotation_rules` - Rotation rules if rotation is enabled. +* `tags` - Tags of the secret. diff --git a/website/docs/d/secretsmanager_secret_version.html.markdown b/website/docs/d/secretsmanager_secret_version.html.markdown new file mode 100644 index 00000000000..3ba19c58483 --- /dev/null +++ b/website/docs/d/secretsmanager_secret_version.html.markdown @@ -0,0 +1,44 @@ +--- +layout: "aws" +page_title: "AWS: aws_secretsmanager_secret_version" +sidebar_current: "docs-aws-datasource-secretsmanager-secret-version" +description: |- + Retrieve information about a Secrets Manager secret version including its secret value +--- + +# Data Source: aws_secretsmanager_secret_version + +Retrieve information about a Secrets Manager secret version includings its secret value. To retrieve secret metadata, see the [`aws_secretsmanager_secret` data source](/docs/providers/aws/d/secretsmanager_secret.html). + +## Example Usage + +### Retrieve Current Secret Version + +By default, this data sources retrieves information based on the `AWSCURRENT` staging label. + +```hcl +data "aws_secretsmanager_secret_version" "example" { + secret_id = "${data.aws_secretsmanager_secret.example.id}" +} +``` + +### Retrieve Specific Secret Version + +```hcl +data "aws_secretsmanager_secret_version" "by-version-stage" { + secret_id = "${data.aws_secretsmanager_secret.example.id}" + version_stage = "example" +} +``` + +## Argument Reference + +* `secret_id` - (Required) Specifies the secret containing the version that you want to retrieve. You can specify either the Amazon Resource Name (ARN) or the friendly name of the secret. +* `version_id` - (Optional) Specifies the unique identifier of the version of the secret that you want to retrieve. Overrides `version_stage`. +* `version_stage` - (Optional) Specifies the secret version that you want to retrieve by the staging label attached to the version. Defaults to `AWSCURRENT`. + +## Attributes Reference + +* `id` - The unique identifier of this version of the secret. +* `secret_string` - The decrypted part of the protected secret information that was originally provided as a string. +* `version_id` - The unique identifier of this version of the secret. diff --git a/website/docs/r/secretsmanager_secret.html.markdown b/website/docs/r/secretsmanager_secret.html.markdown new file mode 100644 index 00000000000..909c977157f --- /dev/null +++ b/website/docs/r/secretsmanager_secret.html.markdown @@ -0,0 +1,68 @@ +--- +layout: "aws" +page_title: "AWS: aws_secretsmanager_secret" +sidebar_current: "docs-aws-resource-secretsmanager-secret" +description: |- + Provides a resource to manage AWS Secrets Manager secret metadata +--- + +# aws_secretsmanager_secret + +Provides a resource to manage AWS Secrets Manager secret metadata. To manage a secret value, see the [`aws_secretsmanager_secret_version` resource](/docs/providers/aws/r/secretsmanager_secret_version.html). + +## Example Usage + +### Basic + +```hcl +resource "aws_secretsmanager_secret" "example" { + name = "example" +} +``` + +### Rotation Configuration + +~> **NOTE:** Configuring rotation causes the secret to rotate once as soon as you store the secret. Before you do this, you must ensure that all of your applications that use the credentials stored in the secret are updated to retrieve the secret from AWS Secrets Manager. The old credentials might no longer be usable after the initial rotation and any applications that you fail to update will break as soon as the old credentials are no longer valid. + +~> **NOTE:** If you cancel a rotation that is in progress (by removing the `rotation` configuration), it can leave the VersionStage labels in an unexpected state. Depending on what step of the rotation was in progress, you might need to remove the staging label AWSPENDING from the partially created version, specified by the SecretVersionId response value. You should also evaluate the partially rotated new version to see if it should be deleted, which you can do by removing all staging labels from the new version's VersionStage field. + +```hcl +resource "aws_secretsmanager_secret" "rotation-example" { + name = "rotation-example" + rotation_lambda_arn = "${aws_lambda_function.example.arn}" + + rotation_rules { + automatically_after_days = 7 + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) 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: `/_+=.@-` Spaces are not permitted. +* `description` - (Optional) A description of the secret. +* `kms_key_id` - (Optional) Specifies the ARN or alias 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. +* `recovery_window_in_days` - (Optional) Specifies the number of days that AWS Secrets Manager waits before it can delete the secret. This value can range from 7 to 30 days. The default value is 30. +* `rotation_lambda_arn` - (Optional) Specifies the ARN of the Lambda function that can rotate the secret. +* `rotation_rules` - (Optional) A structure that defines the rotation configuration for this secret. Defined below. +* `tags` - (Optional) Specifies a key-value map of user-defined tags that are attached to the secret. + +### rotation_rules + +* `automatically_after_days` - (Required) Specifies the number of days between automatic scheduled rotations of the secret. + +## Attribute Reference + +* `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. + +## Import + +`aws_secretsmanager_secret` can be imported by using the secret Amazon Resource Name (ARN), e.g. + +``` +$ terraform import aws_secretsmanager_secret.example arn:aws:secretsmanager:us-east-1:123456789012:secret:example-123456 +``` diff --git a/website/docs/r/secretsmanager_secret_version.html.markdown b/website/docs/r/secretsmanager_secret_version.html.markdown new file mode 100644 index 00000000000..231bdd1178a --- /dev/null +++ b/website/docs/r/secretsmanager_secret_version.html.markdown @@ -0,0 +1,45 @@ +--- +layout: "aws" +page_title: "AWS: aws_secretsmanager_secret_version" +sidebar_current: "docs-aws-resource-secretsmanager-secret-version" +description: |- + Provides a resource to manage AWS Secrets Manager secret version including its secret value +--- + +# aws_secretsmanager_secret_version + +Provides a resource to manage AWS Secrets Manager secret version including its secret value. To manage secret metadata, see the [`aws_secretsmanager_secret` resource](/docs/providers/aws/r/secretsmanager_secret.html). + +~> **NOTE:** If the `AWSCURRENT` staging label is present on this version during resource deletion, that label cannot be removed and will be skipped to prevent errors when fully deleting the secret. That label will leave this secret version active even after the resource is deleted from Terraform unless the secret itself is deleted. Move the `AWSCURRENT` staging label before or after deleting this resource from Terraform to fully trigger version deprecation if necessary. + +## Example Usage + +```hcl +resource "aws_secretsmanager_secret_version" "example" { + secret_id = "${aws_secretsmanager_secret.example.id}" + secret_string = "example-string-to-protect" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `secret_id` - (Required) Specifies the secret to which you want to add a new version. You can specify either the Amazon Resource Name (ARN) or the friendly name of the secret. The secret must already exist. +* `secret_string` - (Required) Specifies text data that you want to encrypt and store in this version of the secret. +* `version_stages` - (Optional) Specifies a list of staging labels that are attached to this version of the secret. A staging label must be unique to a single version of the secret. If you specify a staging label that's already associated with a different version of the same secret then that staging label is automatically removed from the other version and attached to this version. If you do not specify a value, then AWS Secrets Manager automatically moves the staging label `AWSCURRENT` to this new version on creation. + +~> **NOTE:** If `version_stages` is configured, you must include the `AWSCURRENT` staging label if this secret version is the only version or if the label is currently present on this secret version, otherwise Terraform will show a perpetual difference. + +## Attribute Reference + +* `id` - A pipe delimited combination of secret ID and version ID +* `version_id` - The unique identifier of the version of the secret. + +## Import + +`aws_secretsmanager_secret_version` can be imported by using the secret ID and version ID, e.g. + +``` +$ terraform import aws_secretsmanager_secret.example arn:aws:secretsmanager:us-east-1:123456789012:secret:example-123456|xxxxx-xxxxxxx-xxxxxxx-xxxxx +``` From c9ccda055c33f6c6519280fe1e92d4ce97c59a13 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Tue, 24 Apr 2018 12:40:17 -0400 Subject: [PATCH 2/3] resource/aws_secretsmanager_secret: Fix imports formatting --- aws/resource_aws_secretsmanager_secret.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aws/resource_aws_secretsmanager_secret.go b/aws/resource_aws_secretsmanager_secret.go index 22e1598aede..c40c3f6ed15 100644 --- a/aws/resource_aws_secretsmanager_secret.go +++ b/aws/resource_aws_secretsmanager_secret.go @@ -5,10 +5,9 @@ import ( "log" "time" - "github.com/hashicorp/terraform/helper/resource" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" ) From 273efff52ad1dc416bb14240f66510cd9eee29b7 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Tue, 24 Apr 2018 12:41:25 -0400 Subject: [PATCH 3/3] resource/aws_secretsmanager_secret: Use common tagsSchema() --- aws/resource_aws_secretsmanager_secret.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/aws/resource_aws_secretsmanager_secret.go b/aws/resource_aws_secretsmanager_secret.go index c40c3f6ed15..2156cc2a612 100644 --- a/aws/resource_aws_secretsmanager_secret.go +++ b/aws/resource_aws_secretsmanager_secret.go @@ -67,10 +67,7 @@ func resourceAwsSecretsManagerSecret() *schema.Resource { }, }, }, - "tags": { - Type: schema.TypeMap, - Optional: true, - }, + "tags": tagsSchema(), }, } }