diff --git a/.changelog/25721.txt b/.changelog/25721.txt new file mode 100644 index 000000000000..a84959002246 --- /dev/null +++ b/.changelog/25721.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_ssm_parameter: Add `insecure_value` argument to enable dynamic use of SSM parameter values +``` diff --git a/internal/service/ssm/parameter.go b/internal/service/ssm/parameter.go index 4590449d829b..5b0bda284b4a 100644 --- a/internal/service/ssm/parameter.go +++ b/internal/service/ssm/parameter.go @@ -35,17 +35,53 @@ func ResourceParameter() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "name": { + "allowed_pattern": { Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringLenBetween(1, 2048), + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 1024), + }, + "arn": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "data_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + "aws:ec2:image", + "text", + }, false), }, "description": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringLenBetween(0, 1024), }, + "insecure_value": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"insecure_value", "value"}, + }, + "key_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 2048), + }, + "overwrite": { + Type: schema.TypeBool, + Optional: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), "tier": { Type: schema.TypeString, Optional: true, @@ -64,44 +100,16 @@ func ResourceParameter() *schema.Resource { ValidateFunc: validation.StringInSlice(ssm.ParameterType_Values(), false), }, "value": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - }, - "arn": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "key_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "data_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice([]string{ - "aws:ec2:image", - "text", - }, false), - }, - "overwrite": { - Type: schema.TypeBool, - Optional: true, - }, - "allowed_pattern": { Type: schema.TypeString, Optional: true, - ValidateFunc: validation.StringLenBetween(0, 1024), + Sensitive: true, + Computed: true, + ExactlyOneOf: []string{"insecure_value", "value"}, }, "version": { Type: schema.TypeInt, Computed: true, }, - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), }, CustomizeDiff: customdiff.Sequence( @@ -113,6 +121,13 @@ func ResourceParameter() *schema.Resource { customdiff.ComputedIf("version", func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool { return diff.HasChange("value") }), + customdiff.ComputedIf("value", func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool { + return diff.HasChange("insecure_value") + }), + customdiff.ComputedIf("insecure_value", func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool { + return diff.HasChange("value") + }), + verify.SetTagsDiff, ), } @@ -125,10 +140,16 @@ func resourceParameterCreate(d *schema.ResourceData, meta interface{}) error { name := d.Get("name").(string) + value := d.Get("value").(string) + + if v, ok := d.Get("insecure_value").(string); ok && v != "" { + value = v + } + paramInput := &ssm.PutParameterInput{ Name: aws.String(name), Type: aws.String(d.Get("type").(string)), - Value: aws.String(d.Get("value").(string)), + Value: aws.String(value), AllowedPattern: aws.String(d.Get("allowed_pattern").(string)), } @@ -213,9 +234,18 @@ func resourceParameterRead(d *schema.ResourceData, meta interface{}) error { name := aws.StringValue(param.Name) d.Set("name", name) d.Set("type", param.Type) - d.Set("value", param.Value) d.Set("version", param.Version) + if _, ok := d.GetOk("insecure_value"); ok && aws.StringValue(param.Type) != ssm.ParameterTypeSecureString { + d.Set("insecure_value", param.Value) + } else { + d.Set("value", param.Value) + } + + if aws.StringValue(param.Type) == ssm.ParameterTypeSecureString && d.Get("insecure_value").(string) != "" { + return fmt.Errorf("invalid configuration, cannot set type = %s and insecure_value", aws.StringValue(param.Type)) + } + describeParamsInput := &ssm.DescribeParametersInput{ ParameterFilters: []*ssm.ParameterStringFilter{ { @@ -269,11 +299,16 @@ func resourceParameterUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).SSMConn if d.HasChangesExcept("tags", "tags_all") { + value := d.Get("value").(string) + + if v, ok := d.Get("insecure_value").(string); ok && v != "" { + value = v + } paramInput := &ssm.PutParameterInput{ Name: aws.String(d.Get("name").(string)), Type: aws.String(d.Get("type").(string)), Tier: aws.String(d.Get("tier").(string)), - Value: aws.String(d.Get("value").(string)), + Value: aws.String(value), Overwrite: aws.Bool(ShouldUpdateParameter(d)), AllowedPattern: aws.String(d.Get("allowed_pattern").(string)), } diff --git a/internal/service/ssm/parameter_test.go b/internal/service/ssm/parameter_test.go index bd502560c4e2..5d05ecf63ea2 100644 --- a/internal/service/ssm/parameter_test.go +++ b/internal/service/ssm/parameter_test.go @@ -2,6 +2,7 @@ package ssm_test import ( "fmt" + "regexp" "testing" "github.com/aws/aws-sdk-go/aws" @@ -273,7 +274,7 @@ func TestAccSSMParameter_disappears(t *testing.T) { }) } -func TestAccSSMParameter_overwrite(t *testing.T) { +func TestAccSSMParameter_Overwrite_basic(t *testing.T) { var param ssm.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -310,7 +311,7 @@ func TestAccSSMParameter_overwrite(t *testing.T) { } // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/12213 -func TestAccSSMParameter_overwriteCascade(t *testing.T) { +func TestAccSSMParameter_Overwrite_cascade(t *testing.T) { name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resource.ParallelTest(t, resource.TestCase{ @@ -335,7 +336,7 @@ func TestAccSSMParameter_overwriteCascade(t *testing.T) { } // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/18550 -func TestAccSSMParameter_overwriteWithTags(t *testing.T) { +func TestAccSSMParameter_Overwrite_tags(t *testing.T) { var param ssm.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -365,7 +366,7 @@ func TestAccSSMParameter_overwriteWithTags(t *testing.T) { } // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/18550 -func TestAccSSMParameter_noOverwriteWithTags(t *testing.T) { +func TestAccSSMParameter_Overwrite_noOverwriteTags(t *testing.T) { var param ssm.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -395,7 +396,7 @@ func TestAccSSMParameter_noOverwriteWithTags(t *testing.T) { } // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/18550 -func TestAccSSMParameter_updateToOverwriteWithTags(t *testing.T) { +func TestAccSSMParameter_Overwrite_updateToTags(t *testing.T) { var param ssm.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -508,7 +509,7 @@ func TestAccSSMParameter_updateType(t *testing.T) { }) } -func TestAccSSMParameter_updateDescription(t *testing.T) { +func TestAccSSMParameter_Overwrite_updateDescription(t *testing.T) { var param ssm.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -604,7 +605,7 @@ func TestAccSSMParameter_fullPath(t *testing.T) { }) } -func TestAccSSMParameter_secure(t *testing.T) { +func TestAccSSMParameter_Secure_basic(t *testing.T) { var param ssm.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -634,6 +635,85 @@ func TestAccSSMParameter_secure(t *testing.T) { }) } +func TestAccSSMParameter_Secure_insecure(t *testing.T) { + var param ssm.Parameter + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssm_parameter.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ssm.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckParameterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccParameterConfig_insecure(rName, "String", "notsecret"), + Check: resource.ComposeTestCheckFunc( + testAccCheckParameterExists(resourceName, ¶m), + resource.TestCheckResourceAttr(resourceName, "insecure_value", "notsecret"), + resource.TestCheckResourceAttr(resourceName, "type", "String"), + ), + }, + { + Config: testAccParameterConfig_insecure(rName, "String", "newvalue"), + Check: resource.ComposeTestCheckFunc( + testAccCheckParameterExists(resourceName, ¶m), + resource.TestCheckResourceAttr(resourceName, "insecure_value", "newvalue"), + resource.TestCheckResourceAttr(resourceName, "type", "String"), + ), + }, + { + Config: testAccParameterConfig_insecure(rName, "String", "diff"), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, + { + Config: testAccParameterConfig_insecure(rName, "SecureString", "notsecret"), + ExpectError: regexp.MustCompile("invalid configuration"), + }, + }, + }) +} + +func TestAccSSMParameter_Secure_insecureChangeSecure(t *testing.T) { + var param ssm.Parameter + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssm_parameter.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ssm.EndpointsID), + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccCheckParameterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccParameterConfig_insecure(rName, "String", "notsecret"), + Check: resource.ComposeTestCheckFunc( + testAccCheckParameterExists(resourceName, ¶m), + resource.TestCheckResourceAttr(resourceName, "insecure_value", "notsecret"), + resource.TestCheckResourceAttr(resourceName, "type", "String"), + ), + }, + { + Config: testAccParameterConfig_secure(rName, "newvalue"), + Check: resource.ComposeTestCheckFunc( + testAccCheckParameterExists(resourceName, ¶m), + resource.TestCheckResourceAttr(resourceName, "value", "newvalue"), + resource.TestCheckResourceAttr(resourceName, "type", "SecureString"), + ), + }, + { + Config: testAccParameterConfig_insecure(rName, "String", "atlantis"), + Check: resource.ComposeTestCheckFunc( + testAccCheckParameterExists(resourceName, ¶m), + resource.TestCheckResourceAttr(resourceName, "insecure_value", "atlantis"), + resource.TestCheckResourceAttr(resourceName, "type", "String"), + ), + }, + }, + }) +} + func TestAccSSMParameter_DataType_ec2Image(t *testing.T) { var param ssm.Parameter rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -662,11 +742,11 @@ func TestAccSSMParameter_DataType_ec2Image(t *testing.T) { }) } -func TestAccSSMParameter_secureWithKey(t *testing.T) { +func TestAccSSMParameter_Secure_key(t *testing.T) { var param ssm.Parameter randString := sdkacctest.RandString(10) name := fmt.Sprintf("%s_%s", t.Name(), randString) - resourceName := "aws_ssm_parameter.secret_test" + resourceName := "aws_ssm_parameter.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -697,7 +777,7 @@ func TestAccSSMParameter_Secure_keyUpdate(t *testing.T) { var param ssm.Parameter randString := sdkacctest.RandString(10) name := fmt.Sprintf("%s_%s", t.Name(), randString) - resourceName := "aws_ssm_parameter.secret_test" + resourceName := "aws_ssm_parameter.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -822,6 +902,16 @@ resource "aws_ssm_parameter" "test" { `, rName, pType, value) } +func testAccParameterConfig_insecure(rName, pType, value string) string { + return fmt.Sprintf(` +resource "aws_ssm_parameter" "test" { + name = %[1]q + type = %[2]q + insecure_value = %[3]q +} +`, rName, pType, value) +} + func testAccParameterConfig_tier(rName, tier string) string { return fmt.Sprintf(` resource "aws_ssm_parameter" "test" { @@ -943,7 +1033,7 @@ resource "aws_ssm_parameter" "test_downstream" { func testAccParameterConfig_secure(rName string, value string) string { return fmt.Sprintf(` -resource "aws_ssm_parameter" "secret_test" { +resource "aws_ssm_parameter" "test" { name = "test_secure_parameter-%[1]s" description = "description for parameter %[1]s" type = "SecureString" @@ -954,7 +1044,7 @@ resource "aws_ssm_parameter" "secret_test" { func testAccParameterConfig_secureKey(rName string, value string, keyAlias string) string { return fmt.Sprintf(` -resource "aws_ssm_parameter" "secret_test" { +resource "aws_ssm_parameter" "test" { name = "test_secure_parameter-%[1]s" description = "description for parameter %[1]s" type = "SecureString" diff --git a/website/docs/r/ssm_parameter.html.markdown b/website/docs/r/ssm_parameter.html.markdown index 2d019b88fd08..07c78e22fb2f 100644 --- a/website/docs/r/ssm_parameter.html.markdown +++ b/website/docs/r/ssm_parameter.html.markdown @@ -12,7 +12,7 @@ Provides an SSM Parameter resource. ## Example Usage -To store a basic string parameter: +### Basic example ```terraform resource "aws_ssm_parameter" "foo" { @@ -22,7 +22,7 @@ resource "aws_ssm_parameter" "foo" { } ``` -To store an encrypted string using the default SSM KMS key: +### Encrypted string using default SSM KMS key ```terraform resource "aws_db_instance" "default" { @@ -55,35 +55,30 @@ resource "aws_ssm_parameter" "secret" { ## Argument Reference -The following arguments are supported: - -* `name` - (Required) The name of the parameter. If the name contains a path (e.g., any forward slashes (`/`)), it must be fully qualified with a leading forward slash (`/`). For additional requirements and constraints, see the [AWS SSM User Guide](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-parameter-name-constraints.html). -* `type` - (Required) The type of the parameter. Valid types are `String`, `StringList` and `SecureString`. -* `value` - (Required) The value of the parameter. This value is always marked as sensitive in the Terraform plan output, regardless of `type`. In Terraform CLI version 0.15 and later, this may require additional configuration handling for certain scenarios. For more information, see the [Terraform v0.15 Upgrade Guide](https://www.terraform.io/upgrade-guides/0-15.html#sensitive-output-values). -* `description` - (Optional) The description of the parameter. -* `tier` - (Optional) The parameter tier to assign to the parameter. - If not specified, will use the default parameter tier for the region. - Valid tiers are `Standard`, `Advanced`, and `Intelligent-Tiering`. - Downgrading an `Advanced` tier parameter to `Standard` will recreate the resource. - For more information on parameter tiers, see the [AWS SSM Parameter tier comparison and guide](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-advanced-parameters.html). -* `key_id` - (Optional) The KMS key id or arn for encrypting a SecureString. +The following arguments are required: + +* `name` - (Required) Name of the parameter. If the name contains a path (e.g., any forward slashes (`/`)), it must be fully qualified with a leading forward slash (`/`). For additional requirements and constraints, see the [AWS SSM User Guide](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-parameter-name-constraints.html). +* `type` - (Required) Type of the parameter. Valid types are `String`, `StringList` and `SecureString`. + +The following arguments are optional: + +* `allowed_pattern` - (Optional) Regular expression used to validate the parameter value. +* `data_type` - (Optional) Data type of the parameter. Valid values: `text` and `aws:ec2:image` for AMI format, see the [Native parameter support for Amazon Machine Image IDs](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-ec2-aliases.html). +* `description` - (Optional) Description of the parameter. +* `insecure_value` - (Optional, exactly one of `value` or `insecure_value` is required) Value of the parameter. **Use caution:** This value is _never_ marked as sensitive in the Terraform plan output. This argument is not valid with a `type` of `SecureString`. +* `key_id` - (Optional) KMS key ID or ARN for encrypting a SecureString. * `overwrite` - (Optional) Overwrite an existing parameter. If not specified, will default to `false` if the resource has not been created by terraform to avoid overwrite of existing resource and will default to `true` otherwise (terraform lifecycle rules should then be used to manage the update behavior). -* `allowed_pattern` - (Optional) A regular expression used to validate the parameter value. -* `data_type` - (Optional) The data_type of the parameter. Valid values: text and aws:ec2:image for AMI format, see the [Native parameter support for Amazon Machine Image IDs -](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-ec2-aliases.html) -* `tags` - (Optional) A map of tags to assign to the object. 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. +* `tags` - (Optional) Map of tags to assign to the object. 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. +* `tier` - (Optional) Parameter tier to assign to the parameter. If not specified, will use the default parameter tier for the region. Valid tiers are `Standard`, `Advanced`, and `Intelligent-Tiering`. Downgrading an `Advanced` tier parameter to `Standard` will recreate the resource. For more information on parameter tiers, see the [AWS SSM Parameter tier comparison and guide](https://docs.aws.amazon.com/systems-manager/latest/userguide/parameter-store-advanced-parameters.html). +* `value` - (Optional, exactly one of `value` or `insecure_value` is required) Value of the parameter. This value is always marked as sensitive in the Terraform plan output, regardless of `type`. In Terraform CLI version 0.15 and later, this may require additional configuration handling for certain scenarios. For more information, see the [Terraform v0.15 Upgrade Guide](https://www.terraform.io/upgrade-guides/0-15.html#sensitive-output-values). ## Attributes Reference In addition to all arguments above, the following attributes are exported: -* `arn` - The ARN of the parameter. -* `name` - (Required) The name of the parameter. -* `description` - (Required) The description of the parameter. -* `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). -* `type` - (Required) The type of the parameter. Valid types are `String`, `StringList` and `SecureString`. -* `value` - (Required) The value of the parameter. -* `version` - The version of the parameter. +* `arn` - ARN of the parameter. +* `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). +* `version` - Version of the parameter. ## Import