Repository: hashicorp/terraform-provider-aws
Pull request #42078: r/aws_instance: set  user_data cleartext instead of hash
Author: login: johnsonaj
Labels: name: breaking-change
description: Introduces a breaking change in current functionality; usually deferred to the next major release.
name: size/L
description: Managed by automation to categorize the size of a PR.
name: tests
description: PRs: expanded test coverage. Issues: expanded coverage, enhancements to test infrastructure.
name: service/ec2
description: Issues and PRs that pertain to the ec2 service.
Created: 2025-04-01T20:28:49Z | Merged: 2025-04-02T22:00:34Z

<!---
See what makes a good Pull Request at: https://hashicorp.github.io/terraform-provider-aws/raising-a-pull-request/
--->
### Description
<!---
Please provide a helpful description of what change this pull request will introduce.
--->

- [x] remove `StateFunc` that hashes `user_data` for state normalization
- [x] set cleartext value in state for `user_data`
- [x] ensure that previously hashed values do not cause diff with equivalent cleartext values
- [x] add warning diagnostic to `user_data` if base64 is detected. Redirect practitioners to use `user_data_base64`

### Relations
<!---
If your pull request fully resolves and should automatically close the linked issue, use Closes. Otherwise, use Relates.

For Example:

--->

Closes #7625
Closes #13805

### References
<!---
Optionally, provide any helpful references that may help the reviewer(s).
--->


### Output from Acceptance Testing
<!--
Replace TestAccXXX with a pattern that matches the tests affected by this PR.

Replace ec2 with the service package corresponding to your tests.

For more information on the `-run` flag, see the `go test` documentation at https://tip.golang.org/cmd/go/#hdr-Testing_flags.
-->

```console
% make testacc TESTARGS="-run=TestAccEC2Instance_" PKG=ec2

make: Verifying source code with gofmt...
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go1.24.1 test ./internal/service/ec2/... -v -count 1 -parallel 20  -run=TestAccEC2Instance_ -timeout 360m -vet=off
2025/04/02 12:22:44 Initializing Terraform AWS Provider...
=== NAME  TestAccEC2Instance_cpuOptionsAmdSevSnpCoreThreads
    ec2_instance_test.go:4301: skipping tests; AWS_DEFAULT_REGION (us-west-2) not supported. Supported: [us-east-2]
--- SKIP: TestAccEC2Instance_cpuOptionsAmdSevSnpCoreThreads (0.30s)
=== NAME  TestAccEC2Instance_cpuOptionsAmdSevSnpEnabledToDisabled
    ec2_instance_test.go:4208: skipping tests; AWS_DEFAULT_REGION (us-west-2) not supported. Supported: [us-east-2]
--- SKIP: TestAccEC2Instance_cpuOptionsAmdSevSnpEnabledToDisabled (0.30s)
=== NAME  TestAccEC2Instance_cpuOptionsAmdSevSnpDisabledToEnabled
    ec2_instance_test.go:4252: skipping tests; AWS_DEFAULT_REGION (us-west-2) not supported. Supported: [us-east-2]
--- SKIP: TestAccEC2Instance_cpuOptionsAmdSevSnpDisabledToEnabled (0.31s)
=== NAME  TestAccEC2Instance_cpuOptionsAmdSevSnpUnspecifiedToDisabledToEnabledToUnspecified
    ec2_instance_test.go:4075: skipping tests; AWS_DEFAULT_REGION (us-west-2) not supported. Supported: [us-east-2]
--- SKIP: TestAccEC2Instance_cpuOptionsAmdSevSnpUnspecifiedToDisabledToEnabledToUnspecified (0.31s)
=== NAME  TestAccEC2Instance_cpuOptionsAmdSevSnpUnspecifiedToEnabledToDisabledToUnspecified
    ec2_instance_test.go:4142: skipping tests; AWS_DEFAULT_REGION (us-west-2) not supported. Supported: [us-east-2]
--- SKIP: TestAccEC2Instance_cpuOptionsAmdSevSnpUnspecifiedToEnabledToDisabledToUnspecified (0.31s)
--- PASS: TestAccEC2Instance_LaunchTemplate_basic (69.24s)
--- PASS: TestAccEC2Instance_LaunchTemplate_swapIDAndName (83.37s)
--- PASS: TestAccEC2Instance_LaunchTemplate_setSpecificVersion (105.54s)
--- PASS: TestAccEC2Instance_LaunchTemplateModifyTemplate_defaultVersion (128.55s)
--- PASS: TestAccEC2Instance_tags (143.05s)
--- PASS: TestAccEC2Instance_EBSRootDevice_modifyType (146.66s)
--- PASS: TestAccEC2Instance_GetPasswordData_falseToTrue (159.33s)
--- PASS: TestAccEC2Instance_GetPasswordData_trueToFalse (213.68s)
--- PASS: TestAccEC2Instance_NewNetworkInterface_privateIPAndSecondaryPrivateIPsUpdate (150.16s)
--- PASS: TestAccEC2Instance_NewNetworkInterface_emptyPrivateIPAndSecondaryPrivateIPsUpdate (139.50s)
--- PASS: TestAccEC2Instance_LaunchTemplate_spotAndStop (324.86s)
--- PASS: TestAccEC2Instance_networkCardIndex (327.48s)
--- PASS: TestAccEC2Instance_AssociatePublic_explicitPublic (336.14s)
--- PASS: TestAccEC2Instance_AssociatePublic_defaultPublic (336.69s)
--- PASS: TestAccEC2Instance_LaunchTemplate_iamInstanceProfile (340.05s)
--- PASS: TestAccEC2Instance_AssociatePublic_overridePrivate (346.09s)
--- PASS: TestAccEC2Instance_LaunchTemplate_overrideTemplate (354.36s)
--- PASS: TestAccEC2Instance_AssociatePublic_explicitPrivate (356.30s)
--- PASS: TestAccEC2Instance_AssociatePublic_overridePublic (366.80s)
--- PASS: TestAccEC2Instance_LaunchTemplate_vpcSecurityGroup (372.32s)
--- PASS: TestAccEC2Instance_LaunchTemplate_updateTemplateVersion (397.63s)
--- PASS: TestAccEC2Instance_AssociatePublic_defaultPrivate (330.55s)
--- PASS: TestAccEC2Instance_NewNetworkInterface_emptyPrivateIPAndSecondaryPrivateIPs (352.93s)
--- PASS: TestAccEC2Instance_EBSRootDevice_basic (68.41s)
--- PASS: TestAccEC2Instance_NewNetworkInterface_privateIPAndSecondaryPrivateIPs (312.56s)
--- PASS: TestAccEC2Instance_addSecurityGroupNetworkInterface (295.60s)
--- PASS: TestAccEC2Instance_autoRecovery (96.54s)
--- PASS: TestAccEC2Instance_EBSRootDevice_modifySize (105.13s)
--- PASS: TestAccEC2Instance_addSecondaryInterface (303.12s)
--- PASS: TestAccEC2Instance_primaryNetworkInterfaceSourceDestCheck (313.36s)
--- PASS: TestAccEC2Instance_basicWithSpot (307.70s)
--- PASS: TestAccEC2Instance_CapacityReservationPreference_none (306.98s)
--- PASS: TestAccEC2Instance_CapacityReservationPreference_open (316.81s)
=== NAME  TestAccEC2Instance_CapacityReservation_modifyTarget
    acctest.go:1553: skipping test for aws/us-west-2: Error running apply: exit status 1

        Error: starting EC2 Instance (i-0e51f289cd2bd0edb): operation error EC2: StartInstances, https response error StatusCode: 400, RequestID: a2f8aa5a-c6a5-4c04-ab83-29648a61f51b, api error ReservationCapacityExceeded: The requested reservation does not have sufficient compatible and available capacity for this request.

          with aws_instance.test,
          on terraform_plugin_test.tf line 50, in resource "aws_instance" "test":
          50: resource "aws_instance" "test" {

--- SKIP: TestAccEC2Instance_CapacityReservation_modifyTarget (351.68s)
--- PASS: TestAccEC2Instance_CapacityReservation_targetID (347.86s)
--- PASS: TestAccEC2Instance_UserData_migrate (720.81s)
--- PASS: TestAccEC2Instance_keyPairCheck (312.15s)
--- PASS: TestAccEC2Instance_NewNetworkInterface_publicIPAndSecondaryPrivateIPs (611.58s)
--- PASS: TestAccEC2Instance_Empty_privateIP (309.72s)
--- PASS: TestAccEC2Instance_BlockDeviceTags_defaultTagsVolumeTagsOverlap (87.43s)
--- PASS: TestAccEC2Instance_BlockDeviceTags_defaultTagsRBDOverlap (84.40s)
--- PASS: TestAccEC2Instance_associatePublicIPAndPrivateIP (319.36s)
--- PASS: TestAccEC2Instance_instanceProfileChange (195.28s)
--- PASS: TestAccEC2Instance_PrivateDNSNameOptions_computed (403.26s)
--- PASS: TestAccEC2Instance_BlockDeviceTags_defaultTagsEBSRoot (102.16s)
--- PASS: TestAccEC2Instance_PrivateDNSNameOptions_configured (416.59s)
--- PASS: TestAccEC2Instance_privateIP (309.16s)
--- PASS: TestAccEC2Instance_BlockDeviceTags_ebsAndRoot (113.64s)
--- PASS: TestAccEC2Instance_BlockDeviceTags_defaultTagsVolumeTags (151.17s)
--- PASS: TestAccEC2Instance_ipv6AddressCountAndSingleAddressCausesError (1.03s)
--- PASS: TestAccEC2Instance_BlockDeviceTags_volumeTags (121.29s)
--- PASS: TestAccEC2Instance_IPv6AddressCount (83.00s)
--- PASS: TestAccEC2Instance_BlockDeviceTags_attachedVolume (164.94s)
    ec2_instance_test.go:1121: skipping since no Outposts found
--- SKIP: TestAccEC2Instance_outpost (0.34s)
--- PASS: TestAccEC2Instance_iamInstanceProfile (321.69s)
--- PASS: TestAccEC2Instance_networkInstanceRemovingAllSecurityGroups (131.66s)
--- PASS: TestAccEC2Instance_hibernation (692.88s)
--- PASS: TestAccEC2Instance_iamInstanceProfilePath (345.22s)
--- PASS: TestAccEC2Instance_IPv6_primaryEnable (111.83s)
--- PASS: TestAccEC2Instance_CapacityReservation_modifyPreference (664.00s)
--- PASS: TestAccEC2Instance_BlockDeviceTags_defaultTagsEBDOverlaps (312.59s)
--- PASS: TestAccEC2Instance_disableAPITerminationFinalFalse (62.29s)
--- PASS: TestAccEC2Instance_changeInstanceTypeReplace (640.47s)
--- PASS: TestAccEC2Instance_disableAPIStop (91.76s)
--- PASS: TestAccEC2Instance_changeInstanceType (646.35s)
--- PASS: TestAccEC2Instance_forceNewAndTagsDrift (663.86s)
--- PASS: TestAccEC2Instance_CreditSpecificationT3_updateCPUCredits (118.02s)
--- PASS: TestAccEC2Instance_CreditSpecificationT3_unlimitedCPUCredits (109.55s)
--- PASS: TestAccEC2Instance_networkInstanceVPCSecurityGroupIDs (327.48s)
--- PASS: TestAccEC2Instance_IPv6_supportAddressCountWithIPv4 (311.40s)
--- PASS: TestAccEC2Instance_CreditSpecificationT3_standardCPUCredits (118.92s)
--- PASS: TestAccEC2Instance_networkInstanceSecurityGroups (328.12s)
--- PASS: TestAccEC2Instance_CreditSpecification_updateCPUCredits (128.39s)
--- PASS: TestAccEC2Instance_IPv6_supportAddressCount (311.38s)
--- PASS: TestAccEC2Instance_placementPartitionNumber (310.60s)
--- PASS: TestAccEC2Instance_placementGroup (310.71s)
--- PASS: TestAccEC2Instance_changeInstanceTypeAndUserData (859.54s)
--- PASS: TestAccEC2Instance_disableAPITerminationFinalTrue (310.41s)
--- PASS: TestAccEC2Instance_CreditSpecificationUnknownCPUCredits_t3a (310.32s)
--- PASS: TestAccEC2Instance_changeInstanceTypeAndUserDataBase64 (947.73s)
--- PASS: TestAccEC2Instance_metadataOptions (171.44s)
--- PASS: TestAccEC2Instance_gp3RootBlockDevice (93.03s)
--- PASS: TestAccEC2Instance_CreditSpecification_isNotAppliedToNonBurstable (309.45s)
--- PASS: TestAccEC2Instance_CreditSpecificationT3_unspecifiedDefaultsToUnlimited (321.10s)
--- PASS: TestAccEC2Instance_CreditSpecification_unlimitedCPUCredits (79.81s)
--- PASS: TestAccEC2Instance_CreditSpecification_standardCPUCredits (99.59s)
--- PASS: TestAccEC2Instance_CreditSpecificationUnknownCPUCredits_t4g (320.40s)
--- PASS: TestAccEC2Instance_CapacityReservation_unspecifiedDefaultsToOpen (313.01s)
--- PASS: TestAccEC2Instance_UserData_unspecifiedToEmptyString (297.20s)
--- PASS: TestAccEC2Instance_enclaveOptions (339.64s)
--- PASS: TestAccEC2Instance_EBSRootDevice_modifyAll (116.03s)
--- PASS: TestAccEC2Instance_IPv6_primaryDisable (643.06s)
--- PASS: TestAccEC2Instance_EBSRootDeviceModifyThroughput_gp3 (146.04s)
--- PASS: TestAccEC2Instance_EBSRootDeviceMultipleBlockDevices_modifyDeleteOnTermination (93.14s)
--- PASS: TestAccEC2Instance_EBSRootDevice_modifyDeleteOnTermination (126.85s)
--- PASS: TestAccEC2Instance_cpuOptionsCoreThreadsUnspecifiedToSpecified (109.70s)
--- PASS: TestAccEC2Instance_primaryNetworkInterface (322.70s)
--- PASS: TestAccEC2Instance_CreditSpecificationUnspecifiedToEmpty_nonBurstable (121.00s)
--- PASS: TestAccEC2Instance_CreditSpecification_unspecifiedDefaultsToStandard (320.73s)
--- PASS: TestAccEC2Instance_CreditSpecificationUnlimitedCPUCredits_t2Tot3Taint (602.81s)
--- PASS: TestAccEC2Instance_CreditSpecificationUnknownCPUCredits_t3 (321.40s)
--- PASS: TestAccEC2Instance_CreditSpecificationStandardCPUCredits_t2Tot3Taint (622.53s)
--- PASS: TestAccEC2Instance_CreditSpecificationUnknownCPUCredits_t2 (320.73s)
--- PASS: TestAccEC2Instance_EBSRootDeviceModifyIOPS_io2 (115.53s)
--- PASS: TestAccEC2Instance_gp2WithIopsValue (5.62s)
--- PASS: TestAccEC2Instance_noAMIEphemeralDevices (72.53s)
--- PASS: TestAccEC2Instance_blockDevices (51.95s)
--- PASS: TestAccEC2Instance_cpuOptionsCoreThreadsMigration (78.16s)
--- PASS: TestAccEC2Instance_gp2IopsDevice (52.05s)
--- PASS: TestAccEC2Instance_EBSRootDeviceMultipleBlockDevices_modifySize (123.53s)
--- PASS: TestAccEC2Instance_sourceDestCheck (139.73s)
--- PASS: TestAccEC2Instance_EBSRootDevice_multipleDynamicEBSBlockDevices (317.53s)
--- PASS: TestAccEC2Instance_UserData_ReplaceOnChange_Off_Base64 (592.11s)
--- PASS: TestAccEC2Instance_tags_DefaultTags_updateToResourceOnly (88.30s)
--- PASS: TestAccEC2Instance_cpuOptionsCoreThreads (152.68s)
--- PASS: TestAccEC2Instance_CreditSpecificationEmpty_nonBurstable (310.99s)
--- PASS: TestAccEC2Instance_tags_IgnoreTags_Overlap_ResourceTag (106.90s)
--- PASS: TestAccEC2Instance_UserData_emptyStringToUnspecified (307.41s)
--- PASS: TestAccEC2Instance_UserData_ReplaceOnChange_On_Base64 (622.95s)
--- PASS: TestAccEC2Instance_EBSBlockDevice_invalidThroughputForVolumeType (5.72s)
--- PASS: TestAccEC2Instance_tags_IgnoreTags_Overlap_DefaultTag (110.95s)
--- PASS: TestAccEC2Instance_basic (306.31s)
--- PASS: TestAccEC2Instance_EBSBlockDevice_invalidIopsForVolumeType (6.15s)
--- PASS: TestAccEC2Instance_UserData_ReplaceOnChange_On (644.93s)
--- PASS: TestAccEC2Instance_UserData_ReplaceOnChange_Off (654.40s)
--- PASS: TestAccEC2Instance_tags_ComputedTag_OnUpdate_Add (71.86s)
--- PASS: TestAccEC2Instance_tags_ComputedTag_OnUpdate_Replace (113.79s)
--- PASS: TestAccEC2Instance_tags_DefaultTags_emptyProviderOnlyTag (54.08s)
--- PASS: TestAccEC2Instance_EBSRootDeviceModifyIOPS_io1 (370.57s)
--- PASS: TestAccEC2Instance_tags_ComputedTag_OnCreate (96.61s)
--- PASS: TestAccEC2Instance_EBSBlockDevice_kmsKeyARN (58.23s)
--- PASS: TestAccEC2Instance_EBSBlockDevice_RootBlockDevice_removed (120.43s)
--- PASS: TestAccEC2Instance_tags_DefaultTags_emptyResourceTag (74.21s)
--- PASS: TestAccEC2Instance_tags_EmptyTag_OnUpdate_Replace (121.76s)
--- PASS: TestAccEC2Instance_tags_DefaultTags_updateToProviderOnly (90.68s)
--- PASS: TestAccEC2Instance_tags_AddOnUpdate (62.39s)
--- PASS: TestAccEC2Instance_UserData_basic (341.91s)
--- PASS: TestAccEC2Instance_rootInstanceStore (468.55s)
--- PASS: TestAccEC2Instance_tags_EmptyMap (60.46s)
--- PASS: TestAccEC2Instance_tags_DefaultTags_overlapping (147.64s)
--- PASS: TestAccEC2Instance_RootBlockDevice_kmsKeyARN (310.81s)
--- PASS: TestAccEC2Instance_tags_EmptyTag_OnUpdate_Add (100.63s)
--- PASS: TestAccEC2Instance_userDataBase64 (340.96s)
--- PASS: TestAccEC2Instance_UserData_stringToEncodedString (612.14s)
--- PASS: TestAccEC2Instance_tags_EmptyTag_OnCreate (124.78s)
--- PASS: TestAccEC2Instance_tags_null (80.46s)
--- PASS: TestAccEC2Instance_tags_DefaultTags_providerOnly (148.65s)
--- PASS: TestAccEC2Instance_tags_DefaultTags_nonOverlapping (96.41s)
--- PASS: TestAccEC2Instance_UserData_update (644.61s)
--- PASS: TestAccEC2Instance_tags_DefaultTags_nullNonOverlappingResourceTag (308.75s)
--- PASS: TestAccEC2Instance_inDefaultVPCBySgName (290.13s)
--- PASS: TestAccEC2Instance_inDefaultVPCBySgID (319.85s)
--- PASS: TestAccEC2Instance_atLeastOneOtherEBSVolume (359.04s)
--- PASS: TestAccEC2Instance_disappears (332.20s)
--- PASS: TestAccEC2Instance_tags_DefaultTags_nullOverlappingResourceTag (318.39s)
--- PASS: TestAccEC2Instance_userDataBase64_update (590.75s)
--- PASS: TestAccEC2Instance_userDataBase64_updateWithBashFile (601.41s)
--- PASS: TestAccEC2Instance_userDataBase64_updateWithZipFile (633.50s)
--- PASS: TestAccEC2Instance_dedicatedInstance (3062.23s)
PASS
ok  	github.com/hashicorp/terraform-provider-aws/internal/service/ec2	4010.291s
```


Linked issue #7625: aws_instance user_data as cleartext, not hash
<!--- Please keep this note for the community --->

### Community Note

* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request
* Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
* If you are interested in working on this issue or have submitted a pull request, please leave a comment

<!--- Thank you for keeping this note for the community --->

### Description

The `user_data` attribute on `aws_instance` has always been defined with a special `StateFunc` that causes the result saved in the state (and thus, the values shown in diffs) to be a hash of the configured content, rather than the content itself.

This makes the real before and after values inscrutable in diffs. Historically this didn't matter so much because Terraform's rendering of long, multi-line strings in diffs wasn't very good anyway (hashicorp/terraform#14409), but Terraform v0.12 introduces a specialized rendering for multi-line strings that includes a line-oriented diff description, as shown in the following mockup of an `aws_instance` diff we created before implementation of the feature:

![user_data argument is shown as a line-oriented string diff indicating exactly which lines of the string were changed](https://user-images.githubusercontent.com/20180/34750899-740e1bac-f55e-11e7-804e-4e9ac6bc2822.png)

The `user_data` rendering shown above cannot be achieved with the current `aws_instance` implementation because of the hashing of the given value. Therefore this issue represents figuring out whether we can safely remove the `StateFunc` in a future major release of the AWS provider to expose the actual value from configuration.

There are two particularly tricky aspects to this:

* Since existing state snapshots will include hashes instead of real values, we will need some special handling to avoid erroneously detecting the switch to unhashed data as a change (`user_data` is a `ForceNew` attribute).

  Hashing is lossy, so we cannot fix this with a state upgrade, but hopefully we can address it by adding a `DiffSuppressFunc` that recognizes when the old value is a string of the correct length to potentially be a hash, try hashing the new value, and suppress the diff if the new value hash matches what is stored.

  This should effectively defer making the switch over to storing the full value until the first time the configured value is changed after upgrading. At that point there will be a one-time confusing diff that describes switching from a hash to a literal value, which may be jarring and so should be covered in upgrade notes, but afterwards the diffs should behave as expected.

* `user_data` historically tried to recognize if it was being given base64-encoded input and thus submit it verbatim to the remote API. Subsequently we introduced `user_data_base64` to make that more explicit, as part of adopting base64 as the standard encoding of raw binary data in Terraform configuration. There may be some historical configurations using base64-encoded data in the `user_data` argument which would add some further complexity here; it may work out simpler to remove that legacy support at the same time and have users migrate to using `user_data_base64`, though that may be difficult to achieve without forcing replacement of existing instances.

### New or Affected Resource(s)

* `aws_instance`
* Possibly also `aws_launch_configuration`?

### References

* hashicorp/terraform#15180
* #850
* #4257

Linked issue comments: user: login: glenjamin
created_at: 2020-04-09T10:22:33Z
body: a sort-of workaround for this is to have a use `terraform state show` and or `terraform show` on the cloudinit resource that's being fed into the userdata field - this can be used to give you a before and after view of the generated data

it's a little bit fiddly though, I couldn't find a nice way to get a clean display of the value - some combination of the `-json` flag with `jq` can extract something.
author_association: NONE
user: login: HeroCC
created_at: 2021-06-24T19:57:38Z
body: Another potential workaround for this is to use the `user_data_base64` field instead, then have my output be `base64decode(instance.user_data_base64)`. You can't use gzip (unless a function to undo [`base64gzip`](https://www.terraform.io/docs/language/functions/base64gzip.html) is implemented), but for my use case it isn't important.

For example, with the cloudinit_provider: 
```hcl
data "cloudinit_config" "userdata" {
  for_each = var.instances

  # These values are important
  gzip = false
  base64_encode = true

  part {
    content_type = "text/cloud-config"
    content = templatefile("cloudconfig.yaml", {
      new-hostname = each.key
    })
  }
}

resource "aws_instance" "this" {
  for_each = var.instances
  user_data_base64 = data.cloudinit_config.userdata[each.key].rendered
}

output "cloud-config" {
  description = "The cloudinit config generated for each instance"
  value = {
    for hostname, instance in aws_instance.this : hostname => {
      user_data = base64decode(instance.user_data_base64)
    }
  }
}
```
author_association: NONE
user: login: sblask
created_at: 2021-10-21T01:00:06Z
body: Also related: https://github.com/hashicorp/terraform-provider-aws/issues/13805

Another affected resource where the diff already works on the content (albeit only on base64) and not the hash: aws_launch_template

The workaround with outputs works, but it's a bit painful if the user data isn't tiny because it always shows the whole thing and not just the context around changes.
author_association: NONE
user: login: rubber-ant
created_at: 2023-11-13T11:36:14Z
body: easier workaround 
https://github.com/hashicorp/terraform-provider-aws/issues/13805

add 

```
resource "null_resource" "dummy" {
  triggers = {
    file= file("${path.module}/cloudconfig.sh")
  }
}
```

```
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place
-/+ destroy and then create replacement

Terraform will perform the following actions:


  # module.test_module.null_resource.dummy must be replaced
-/+ resource "null_resource" "dummy" {
      ~ id       = "6886541937855345911" -> (known after apply)
      ~ triggers = { # forces replacement
          ~ "file" = <<-EOT
                #!/bin/bash
```
author_association: NONE
user: login: gdavison
created_at: 2024-04-17T22:23:47Z
body: Because this will store different state in the `user_data` parameter, making this change is technically a breaking change. We can do this work as part of v6.0
author_association: CONTRIBUTOR
user: login: james-callahan
created_at: 2024-11-25T00:10:53Z
body: `user_data` is (unfortunatly) frequently used to pass secrets to an EC2 instance. As such, its value is often sensitive, and hence people have relied on the hash of it in the state (rather than the actual value) to keep their security properties.
If the value *is* changed to clear-text, I expect that many existing processes will have new security issues!
author_association: NONE
user: login: johnsonaj
created_at: 2025-04-02T22:11:33Z
body: Closed by #42078
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-04-02T22:11:45Z
body: > [!WARNING]
> This Issue has been closed, meaning that any additional comments are much easier for the maintainers to miss. Please assume that the maintainers will not see them.
>
> Ongoing conversations amongst community members are welcome, however, the issue will be locked after 30 days. Moving conversations to another venue, such as the [AWS Provider forum](https://discuss.hashicorp.com/c/terraform-providers/tf-aws/33), is recommended. If you have additional concerns, please open a new issue, referencing this one where needed.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-03T02:19:44Z
body: I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
author_association: CONTRIBUTOR

Diff:
diff --git a/.changelog/42078.txt b/.changelog/42078.txt
new file mode 100644
index 000000000000..7860c14435c9
--- /dev/null
+++ b/.changelog/42078.txt
@@ -0,0 +1,3 @@
+```release-note:breaking-change
+resource/aws_instance: `user_data` now displays cleartext instead of a hash. Base64 encoded content should use `user_data_base64` instead.
+```
\ No newline at end of file
diff --git a/internal/service/ec2/ec2_instance.go b/internal/service/ec2/ec2_instance.go
index 9e7fc536b270..05771a7c25ba 100644
--- a/internal/service/ec2/ec2_instance.go
+++ b/internal/service/ec2/ec2_instance.go
@@ -808,7 +808,6 @@ func resourceInstance() *schema.Resource {
 			"user_data": {
 				Type:          schema.TypeString,
 				Optional:      true,
-				Computed:      true,
 				ConflictsWith: []string{"user_data_base64"},
 				DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
 					// Sometimes the EC2 API responds with the equivalent, empty SHA1 sum
@@ -817,17 +816,38 @@ func resourceInstance() *schema.Resource {
 						(old == "" && new == "da39a3ee5e6b4b0d3255bfef95601890afd80709") {
 						return true
 					}
-					return false
-				},
-				StateFunc: func(v any) string {
-					switch v := v.(type) {
-					case string:
-						return userDataHashSum(v)
-					default:
-						return ""
+
+					oldHashExists := old == userDataHashSum(new)
+					base64Encoded := userDataHashSum(old) == userDataHashSum(new)
+					if oldHashExists || base64Encoded {
+						return true
 					}
+					return false
 				},
-				ValidateFunc: validation.StringLenBetween(0, 16384),
+				ValidateDiagFunc: validation.AllDiag(
+					validation.ToDiagFunc(validation.StringLenBetween(0, 16384)),
+					func(i any, path cty.Path) diag.Diagnostics {
+						var diags diag.Diagnostics
+						v, ok := i.(string)
+						if !ok {
+							return sdkdiag.AppendErrorf(diags, "expected type to be string")
+						}
+
+						if _, err := itypes.Base64Decode(v); err == nil {
+							// value is a base46 encoded string
+							return diag.Diagnostics{
+								diag.Diagnostic{
+									Severity:      diag.Warning,
+									Summary:       "Value is base64 encoded",
+									Detail:        "The value is base64 encoded. If you want to use base64 encoding, please use the user_data_base64 argument. user_data attribute is set as cleartext in state",
+									AttributePath: path,
+								},
+							}
+						}
+
+						return diags
+					},
+				),
 			},
 			"user_data_base64": {
 				Type:          schema.TypeString,
@@ -913,7 +933,7 @@ func resourceInstance() *schema.Resource {
 				if diff.Id() != "" && diff.HasChanges(names.AttrInstanceType, "user_data", "user_data_base64") {
 					// user_data is stored in state as a hash.
 					if diff.HasChange("user_data") && !diff.HasChange(names.AttrInstanceType) {
-						if o, n := diff.GetChange("user_data"); userDataHashSum(n.(string)) == o.(string) {
+						if o, n := diff.GetChange("user_data"); n.(string) == o.(string) {
 							return nil
 						}
 					}
@@ -1454,7 +1474,11 @@ func resourceInstanceRead(ctx context.Context, d *schema.ResourceData, meta any)
 			if b64 {
 				d.Set("user_data_base64", attr.UserData.Value)
 			} else {
-				d.Set("user_data", userDataHashSum(aws.ToString(attr.UserData.Value)))
+				data, err := itypes.Base64Decode(aws.ToString(attr.UserData.Value))
+				if err != nil {
+					return sdkdiag.AppendErrorf(diags, "decoding user_data: %s", err)
+				}
+				d.Set("user_data", string(data))
 			}
 		}
 	}
diff --git a/internal/service/ec2/ec2_instance_test.go b/internal/service/ec2/ec2_instance_test.go
index 5b33464b5a07..0dd56964e0a5 100644
--- a/internal/service/ec2/ec2_instance_test.go
+++ b/internal/service/ec2/ec2_instance_test.go
@@ -307,7 +307,7 @@ func TestAccEC2Instance_atLeastOneOtherEBSVolume(t *testing.T) {
 				Config: testAccInstanceConfig_atLeastOneOtherEBSVolume(rName),
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckInstanceExists(ctx, resourceName, &v),
-					resource.TestCheckResourceAttr(resourceName, "user_data", "3dc39dda39be1205215e776bad998da361a5955d"),
+					resource.TestCheckResourceAttr(resourceName, "user_data", "foo:-with-character's"),
 					resource.TestCheckResourceAttr(resourceName, "root_block_device.#", "0"), // This is an instance store AMI
 					resource.TestCheckResourceAttr(resourceName, "ebs_block_device.#", "0"),
 					resource.TestCheckResourceAttr(resourceName, "outpost_arn", ""),
@@ -327,7 +327,7 @@ func TestAccEC2Instance_atLeastOneOtherEBSVolume(t *testing.T) {
 				Config: testAccInstanceConfig_atLeastOneOtherEBSVolume(rName),
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckInstanceExists(ctx, resourceName, &v),
-					resource.TestCheckResourceAttr(resourceName, "user_data", "3dc39dda39be1205215e776bad998da361a5955d"),
+					resource.TestCheckResourceAttr(resourceName, "user_data", "foo:-with-character's"),
 					resource.TestCheckResourceAttr(resourceName, "ebs_block_device.#", "0"),
 				),
 			},
@@ -1094,7 +1094,6 @@ func TestAccEC2Instance_dedicatedInstance(t *testing.T) {
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckInstanceExists(ctx, resourceName, &v),
 					resource.TestCheckResourceAttr(resourceName, "tenancy", "dedicated"),
-					resource.TestCheckResourceAttr(resourceName, "user_data", "562a3e32810edf6ff09994f050f12e799452379d"),
 				),
 			},
 			{
@@ -1164,7 +1163,7 @@ func TestAccEC2Instance_placementGroup(t *testing.T) {
 				ResourceName:            resourceName,
 				ImportState:             true,
 				ImportStateVerify:       true,
-				ImportStateVerifyIgnore: []string{"user_data_replace_on_change"},
+				ImportStateVerifyIgnore: []string{"user_data_replace_on_change", "user_data"},
 			},
 		},
 	})
@@ -1194,7 +1193,7 @@ func TestAccEC2Instance_placementPartitionNumber(t *testing.T) {
 				ResourceName:            resourceName,
 				ImportState:             true,
 				ImportStateVerify:       true,
-				ImportStateVerifyIgnore: []string{"user_data_replace_on_change"},
+				ImportStateVerifyIgnore: []string{"user_data_replace_on_change", "user_data"},
 			},
 		},
 	})
@@ -2497,11 +2496,6 @@ func TestAccEC2Instance_changeInstanceTypeAndUserData(t *testing.T) {
 	resourceName := "aws_instance.test"
 	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
 
-	hash := sha1.Sum([]byte("hello world"))
-	expectedUserData := hex.EncodeToString(hash[:])
-	hash2 := sha1.Sum([]byte("new world"))
-	expectedUserDataUpdated := hex.EncodeToString(hash2[:])
-
 	resource.ParallelTest(t, resource.TestCase{
 		PreCheck:                 func() { acctest.PreCheck(ctx, t) },
 		ErrorCheck:               acctest.ErrorCheck(t, names.EC2ServiceID),
@@ -2528,7 +2522,7 @@ func TestAccEC2Instance_changeInstanceTypeAndUserData(t *testing.T) {
 					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrInstanceType), knownvalue.StringExact("t2.medium")),
 					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("public_dns"), knownvalue.StringExact("")),
 					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("public_ip"), knownvalue.StringExact("")),
-					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact(expectedUserData)),
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("hello world")),
 				},
 			},
 			{
@@ -2553,7 +2547,7 @@ func TestAccEC2Instance_changeInstanceTypeAndUserData(t *testing.T) {
 					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrInstanceType), knownvalue.StringExact("t2.large")),
 					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("public_dns"), knownvalue.StringExact("")),
 					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("public_ip"), knownvalue.StringExact("")),
-					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact(expectedUserDataUpdated)),
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("new world")),
 				},
 			},
 			{
@@ -5086,7 +5080,68 @@ func TestAccEC2Instance_CreditSpecificationUnlimitedCPUCredits_t2Tot3Taint(t *te
 	})
 }
 
-func TestAccEC2Instance_UserData(t *testing.T) {
+func TestAccEC2Instance_UserData_migrate(t *testing.T) {
+	ctx := acctest.Context(t)
+	var v awstypes.Instance
+	resourceName := "aws_instance.test"
+	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+	hash := sha1.Sum([]byte("hello world"))
+	expectedUserData := hex.EncodeToString(hash[:])
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { acctest.PreCheck(ctx, t) },
+		ErrorCheck:   acctest.ErrorCheck(t, names.EC2ServiceID),
+		CheckDestroy: testAccCheckInstanceDestroy(ctx),
+		Steps: []resource.TestStep{
+			{
+				ExternalProviders: map[string]resource.ExternalProvider{
+					"aws": {
+						Source:            "hashicorp/aws",
+						VersionConstraint: "5.93.0",
+					},
+				},
+				Config: testAccInstanceConfig_userData(rName, "hello world"),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckInstanceExists(ctx, resourceName, &v),
+				),
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact(expectedUserData)),
+				},
+			},
+			{
+				ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+				Config:                   testAccInstanceConfig_userData(rName, "hello world"),
+				ConfigPlanChecks: resource.ConfigPlanChecks{
+					PreApply: []plancheck.PlanCheck{
+						plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop),
+					},
+				},
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("hello world")),
+				},
+			},
+			{
+				// test for migration path from user_data to user_data_base64
+				ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+				Config:                   testAccInstanceConfig_userDataBase64(rName, "hello world"),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckInstanceExists(ctx, resourceName, &v),
+				),
+				ConfigPlanChecks: resource.ConfigPlanChecks{
+					PreApply: []plancheck.PlanCheck{
+						plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
+					},
+				},
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("")),
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data_base64"), knownvalue.StringExact("aGVsbG8gd29ybGQ=")),
+				},
+			},
+		},
+	})
+}
+
+func TestAccEC2Instance_UserData_basic(t *testing.T) {
 	ctx := acctest.Context(t)
 	var v awstypes.Instance
 	resourceName := "aws_instance.test"
@@ -5103,12 +5158,15 @@ func TestAccEC2Instance_UserData(t *testing.T) {
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckInstanceExists(ctx, resourceName, &v),
 				),
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("hello world")),
+				},
 			},
 			{
 				ResourceName:            resourceName,
 				ImportState:             true,
 				ImportStateVerify:       true,
-				ImportStateVerifyIgnore: []string{"user_data", "user_data_replace_on_change"},
+				ImportStateVerifyIgnore: []string{"user_data_replace_on_change"},
 			},
 		},
 	})
@@ -5131,18 +5189,24 @@ func TestAccEC2Instance_UserData_update(t *testing.T) {
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckInstanceExists(ctx, resourceName, &v),
 				),
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("hello world")),
+				},
 			},
 			{
 				Config: testAccInstanceConfig_userData(rName, "new world"),
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckInstanceExists(ctx, resourceName, &v),
 				),
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("new world")),
+				},
 			},
 			{
 				ResourceName:            resourceName,
 				ImportState:             true,
 				ImportStateVerify:       true,
-				ImportStateVerifyIgnore: []string{"user_data", "user_data_replace_on_change"},
+				ImportStateVerifyIgnore: []string{"user_data_replace_on_change"},
 			},
 		},
 	})
@@ -5250,7 +5314,7 @@ func TestAccEC2Instance_UserData_unspecifiedToEmptyString(t *testing.T) {
 	})
 }
 
-func TestAccEC2Instance_UserDataReplaceOnChange_On(t *testing.T) {
+func TestAccEC2Instance_UserData_ReplaceOnChange_On(t *testing.T) {
 	ctx := acctest.Context(t)
 	var instance1, instance2 awstypes.Instance
 	resourceName := "aws_instance.test"
@@ -5267,6 +5331,9 @@ func TestAccEC2Instance_UserDataReplaceOnChange_On(t *testing.T) {
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckInstanceExists(ctx, resourceName, &instance1),
 				),
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("TestData1")),
+				},
 			},
 			{
 				ResourceName:            resourceName,
@@ -5279,14 +5346,21 @@ func TestAccEC2Instance_UserDataReplaceOnChange_On(t *testing.T) {
 				Config: testAccInstanceConfig_userDataSpecifiedReplaceFlag(rName, "TestData2", acctest.CtTrue),
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckInstanceExists(ctx, resourceName, &instance2),
-					testAccCheckInstanceRecreated(&instance1, &instance2),
 				),
+				ConfigPlanChecks: resource.ConfigPlanChecks{
+					PreApply: []plancheck.PlanCheck{
+						plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionReplace),
+					},
+				},
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("TestData2")),
+				},
 			},
 		},
 	})
 }
 
-func TestAccEC2Instance_UserDataReplaceOnChange_On_Base64(t *testing.T) {
+func TestAccEC2Instance_UserData_ReplaceOnChange_On_Base64(t *testing.T) {
 	ctx := acctest.Context(t)
 	var instance1, instance2 awstypes.Instance
 	resourceName := "aws_instance.test"
@@ -5315,14 +5389,18 @@ func TestAccEC2Instance_UserDataReplaceOnChange_On_Base64(t *testing.T) {
 				Config: testAccInstanceConfig_userData64SpecifiedReplaceFlag(rName, "3dc39dda39be1205215e776bad998da361a5955e", acctest.CtTrue),
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckInstanceExists(ctx, resourceName, &instance2),
-					testAccCheckInstanceRecreated(&instance1, &instance2),
 				),
+				ConfigPlanChecks: resource.ConfigPlanChecks{
+					PreApply: []plancheck.PlanCheck{
+						plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionReplace),
+					},
+				},
 			},
 		},
 	})
 }
 
-func TestAccEC2Instance_UserDataReplaceOnChange_Off(t *testing.T) {
+func TestAccEC2Instance_UserData_ReplaceOnChange_Off(t *testing.T) {
 	ctx := acctest.Context(t)
 	var instance1, instance2 awstypes.Instance
 	resourceName := "aws_instance.test"
@@ -5339,6 +5417,9 @@ func TestAccEC2Instance_UserDataReplaceOnChange_Off(t *testing.T) {
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckInstanceExists(ctx, resourceName, &instance1),
 				),
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("TestData1")),
+				},
 			},
 			{
 				ResourceName:            resourceName,
@@ -5353,12 +5434,20 @@ func TestAccEC2Instance_UserDataReplaceOnChange_Off(t *testing.T) {
 					testAccCheckInstanceExists(ctx, resourceName, &instance2),
 					testAccCheckInstanceNotRecreated(&instance1, &instance2),
 				),
+				ConfigPlanChecks: resource.ConfigPlanChecks{
+					PreApply: []plancheck.PlanCheck{
+						plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
+					},
+				},
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("TestData2")),
+				},
 			},
 		},
 	})
 }
 
-func TestAccEC2Instance_UserDataReplaceOnChange_Off_Base64(t *testing.T) {
+func TestAccEC2Instance_UserData_ReplaceOnChange_Off_Base64(t *testing.T) {
 	ctx := acctest.Context(t)
 	var instance1, instance2 awstypes.Instance
 	resourceName := "aws_instance.test"
@@ -5389,6 +5478,11 @@ func TestAccEC2Instance_UserDataReplaceOnChange_Off_Base64(t *testing.T) {
 					testAccCheckInstanceExists(ctx, resourceName, &instance2),
 					testAccCheckInstanceNotRecreated(&instance1, &instance2),
 				),
+				ConfigPlanChecks: resource.ConfigPlanChecks{
+					PreApply: []plancheck.PlanCheck{
+						plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
+					},
+				},
 			},
 		},
 	})
@@ -6877,7 +6971,7 @@ resource "aws_instance" "test" {
   associate_public_ip_address = true
   tenancy                     = "dedicated"
   # pre-encoded base64 data
-  user_data = "3dc39dda39be1205215e776bad998da361a5955d"
+  user_data_base64 = "3dc39dda39be1205215e776bad998da361a5955d"
 
   tags = {
     Name = %[1]q
@@ -6953,7 +7047,7 @@ resource "aws_instance" "test" {
   placement_group             = aws_placement_group.test.name
 
   # pre-encoded base64 data
-  user_data = "3dc39dda39be1205215e776bad998da361a5955d"
+  user_data_base64 = "3dc39dda39be1205215e776bad998da361a5955d"
 
   tags = {
     Name = %[1]q
@@ -6983,7 +7077,7 @@ resource "aws_instance" "test" {
   placement_partition_number  = 3
 
   # pre-encoded base64 data
-  user_data = "3dc39dda39be1205215e776bad998da361a5955d"
+  user_data_base64 = "3dc39dda39be1205215e776bad998da361a5955d"
 
   tags = {
     Name = %[1]q
diff --git a/website/docs/guides/version-6-upgrade.html.markdown b/website/docs/guides/version-6-upgrade.html.markdown
index b030a567991b..22168dde6ba0 100644
--- a/website/docs/guides/version-6-upgrade.html.markdown
+++ b/website/docs/guides/version-6-upgrade.html.markdown
@@ -22,6 +22,7 @@ Upgrade topics:
 - [data-source/aws_batch_compute_environment](#data-sourceaws_batch_compute_environment)
 - [resource/aws_batch_compute_environment](#resourceaws_batch_compute_environment)
 - [resource/aws_cloudfront_response_headers_policy](#resourceaws_cloudfront_response_headers_policy)
+- [resource/aws_instance](#resourceaws_instance)
 - [resource/aws_redshift_cluster](#resourceaws_redshift_cluster)
 - [resource/aws_redshift_service_account](#resourceaws_redshift_service_account)
 - [resource/aws_spot_instance_request](#resourceaws_spot_instance_request)
@@ -122,6 +123,10 @@ As the AWS OpsWorks Stacks service has reached [End Of Life](https://docs.aws.am
 
 * The `etag` argument is now computed only.
 
+## resource/aws_instance
+
+* The `user_data` attribute no longer applies hashing and is now stored in clear text. **Do not include passwords or sensitive information** in `user_data`, as it will be visible in plaintext. Follow [AWS Best Practices](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) to secure your instance metadata. If you need to provide base64-encoded user data, use the `user_data_base64` attribute instead.
+
 ## resource/aws_redshift_cluster
 
 * The `publicly_accessible` attribute now defaults to `false`.
@@ -136,15 +141,6 @@ The `aws_redshift_service_account` resource has been removed. AWS [recommends](h
 
 * Remove `block_duration_minutes` from configuration as it no longer exists.
 
-## resource/aws_batch_compute_environment
-
-* `compute_environment_name` has been renamed to `name`.
-* `compute_environment_name_prefix` has been renamed to `name_prefix`.
-
-## resource/aws_batch_comptete_environment_data_source
-
-* `compute_environment_name` has been renamed to `name`.
-
 ## datasource/aws_globalaccelerator_accelerator
 
 * `id` is now computed only.


Reviews:
user: login: johnsonaj
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:02:04Z
user: login: YakDriver
body: Few comments but looks great!
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:15:49Z
user: login: YakDriver
body: LGTM 🎉 

```console
% make t T=TestAccEC2Instance_UserData K=ec2  
make: Verifying source code with gofmt...
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go1.24.1 test ./internal/service/ec2/... -v -count 1 -parallel 20 -run='TestAccEC2Instance_UserData'  -timeout 360m -vet=off
2025/04/02 17:09:56 Initializing Terraform AWS Provider...
=== RUN   TestAccEC2Instance_UserData_migrate
=== PAUSE TestAccEC2Instance_UserData_migrate
=== RUN   TestAccEC2Instance_UserData_basic
=== PAUSE TestAccEC2Instance_UserData_basic
=== RUN   TestAccEC2Instance_UserData_update
=== PAUSE TestAccEC2Instance_UserData_update
=== RUN   TestAccEC2Instance_UserData_stringToEncodedString
=== PAUSE TestAccEC2Instance_UserData_stringToEncodedString
=== RUN   TestAccEC2Instance_UserData_emptyStringToUnspecified
=== PAUSE TestAccEC2Instance_UserData_emptyStringToUnspecified
=== RUN   TestAccEC2Instance_UserData_unspecifiedToEmptyString
=== PAUSE TestAccEC2Instance_UserData_unspecifiedToEmptyString
=== RUN   TestAccEC2Instance_UserData_ReplaceOnChange_On
=== PAUSE TestAccEC2Instance_UserData_ReplaceOnChange_On
=== RUN   TestAccEC2Instance_UserData_ReplaceOnChange_On_Base64
=== PAUSE TestAccEC2Instance_UserData_ReplaceOnChange_On_Base64
=== RUN   TestAccEC2Instance_UserData_ReplaceOnChange_Off
=== PAUSE TestAccEC2Instance_UserData_ReplaceOnChange_Off
=== RUN   TestAccEC2Instance_UserData_ReplaceOnChange_Off_Base64
=== PAUSE TestAccEC2Instance_UserData_ReplaceOnChange_Off_Base64
=== CONT  TestAccEC2Instance_UserData_migrate
=== CONT  TestAccEC2Instance_UserData_unspecifiedToEmptyString
=== CONT  TestAccEC2Instance_UserData_stringToEncodedString
=== CONT  TestAccEC2Instance_UserData_emptyStringToUnspecified
=== CONT  TestAccEC2Instance_UserData_update
=== CONT  TestAccEC2Instance_UserData_ReplaceOnChange_On
=== CONT  TestAccEC2Instance_UserData_ReplaceOnChange_Off
=== CONT  TestAccEC2Instance_UserData_ReplaceOnChange_Off_Base64
=== CONT  TestAccEC2Instance_UserData_basic
=== CONT  TestAccEC2Instance_UserData_ReplaceOnChange_On_Base64
--- PASS: TestAccEC2Instance_UserData_emptyStringToUnspecified (98.95s)
--- PASS: TestAccEC2Instance_UserData_unspecifiedToEmptyString (109.40s)
--- PASS: TestAccEC2Instance_UserData_basic (326.58s)
--- PASS: TestAccEC2Instance_UserData_ReplaceOnChange_Off (610.58s)
--- PASS: TestAccEC2Instance_UserData_stringToEncodedString (618.79s)
--- PASS: TestAccEC2Instance_UserData_ReplaceOnChange_On (620.66s)
--- PASS: TestAccEC2Instance_UserData_ReplaceOnChange_On_Base64 (640.48s)
--- PASS: TestAccEC2Instance_UserData_update (649.98s)
--- PASS: TestAccEC2Instance_UserData_ReplaceOnChange_Off_Base64 (680.54s)
--- PASS: TestAccEC2Instance_UserData_migrate (948.14s)
PASS
ok  	github.com/hashicorp/terraform-provider-aws/internal/service/ec2	953.909s
```
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-02T21:27:23Z

Review comments:
diff_hunk: @@ -136,15 +141,6 @@ The `aws_redshift_service_account` resource has been removed. AWS [recommends](h
 
 * Remove `block_duration_minutes` from configuration as it no longer exists.
 
-## resource/aws_batch_compute_environment
-
-* `compute_environment_name` has been renamed to `name`.
-* `compute_environment_name_prefix` has been renamed to `name_prefix`.
-
-## resource/aws_batch_comptete_environment_data_source
-
-* `compute_environment_name` has been renamed to `name`.
-
path: website/docs/guides/version-6-upgrade.html.markdown
user: login: johnsonaj
body: duplicate entries
created_at: 2025-04-02T21:02:04Z
author_association: MEMBER
diff_hunk: @@ -5086,7 +5080,68 @@ func TestAccEC2Instance_CreditSpecificationUnlimitedCPUCredits_t2Tot3Taint(t *te
 	})
 }
 
-func TestAccEC2Instance_UserData(t *testing.T) {
+func TestAccEC2Instance_UserData_migrate(t *testing.T) {
+	ctx := acctest.Context(t)
+	var v awstypes.Instance
+	resourceName := "aws_instance.test"
+	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+	hash := sha1.Sum([]byte("hello world"))
+	expectedUserData := hex.EncodeToString(hash[:])
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { acctest.PreCheck(ctx, t) },
+		ErrorCheck:   acctest.ErrorCheck(t, names.EC2ServiceID),
+		CheckDestroy: testAccCheckInstanceDestroy(ctx),
+		Steps: []resource.TestStep{
+			{
+				ExternalProviders: map[string]resource.ExternalProvider{
+					"aws": {
+						Source:            "hashicorp/aws",
+						VersionConstraint: "5.93.0",
+					},
+				},
+				Config: testAccInstanceConfig_userData(rName, "hello world"),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckInstanceExists(ctx, resourceName, &v),
+				),
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact(expectedUserData)),
+				},
+			},
+			{
+				ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+				Config:                   testAccInstanceConfig_userData(rName, "hello world"),
+				ConfigPlanChecks: resource.ConfigPlanChecks{
+					PreApply: []plancheck.PlanCheck{
+						plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop),
path: internal/service/ec2/ec2_instance_test.go
user: login: YakDriver
body: Nice 👍 Making sure it wasn't recreated.
created_at: 2025-04-02T21:05:49Z
author_association: MEMBER
diff_hunk: @@ -5086,7 +5080,68 @@ func TestAccEC2Instance_CreditSpecificationUnlimitedCPUCredits_t2Tot3Taint(t *te
 	})
 }
 
-func TestAccEC2Instance_UserData(t *testing.T) {
+func TestAccEC2Instance_UserData_migrate(t *testing.T) {
+	ctx := acctest.Context(t)
+	var v awstypes.Instance
+	resourceName := "aws_instance.test"
+	rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+	hash := sha1.Sum([]byte("hello world"))
+	expectedUserData := hex.EncodeToString(hash[:])
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck:     func() { acctest.PreCheck(ctx, t) },
+		ErrorCheck:   acctest.ErrorCheck(t, names.EC2ServiceID),
+		CheckDestroy: testAccCheckInstanceDestroy(ctx),
+		Steps: []resource.TestStep{
+			{
+				ExternalProviders: map[string]resource.ExternalProvider{
+					"aws": {
+						Source:            "hashicorp/aws",
+						VersionConstraint: "5.93.0",
+					},
+				},
+				Config: testAccInstanceConfig_userData(rName, "hello world"),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckInstanceExists(ctx, resourceName, &v),
+				),
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact(expectedUserData)),
+				},
+			},
+			{
+				ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+				Config:                   testAccInstanceConfig_userData(rName, "hello world"),
+				ConfigPlanChecks: resource.ConfigPlanChecks{
+					PreApply: []plancheck.PlanCheck{
+						plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop),
+					},
+				},
+				ConfigStateChecks: []statecheck.StateCheck{
+					statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("user_data"), knownvalue.StringExact("hello world")),
+				},
+			},
+			{
+				// test for migration path from user_data to user_data_base64
+				ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+				Config:                   testAccInstanceConfig_userDataBase64(rName, "hello world"),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckInstanceExists(ctx, resourceName, &v),
+				),
+				ConfigPlanChecks: resource.ConfigPlanChecks{
+					PreApply: []plancheck.PlanCheck{
+						plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
path: internal/service/ec2/ec2_instance_test.go
user: login: YakDriver
body: Still not recreated 👍 
created_at: 2025-04-02T21:07:03Z
author_association: MEMBER
diff_hunk: @@ -19,6 +19,7 @@ Upgrade topics:
 - [Dropping Support For Amazon SimpleDB](#dropping-support-for-amazon-simpledb)
 - [Dropping Support For Amazon Worklink](#dropping-support-for-amazon-worklink)
 - [AWS OpsWorks Stacks End of Life](#aws-opsworks-stacks-end-of-life)
+- [resource/aws_instance](#resourceaws_instance)
path: website/docs/guides/version-6-upgrade.html.markdown
user: login: YakDriver
body: I would alphabetize this. Or do we do them in order of RUM usage? This is one of the most important so I can see putting it first.
created_at: 2025-04-02T21:11:38Z
author_association: MEMBER
diff_hunk: @@ -122,6 +123,10 @@ As the AWS OpsWorks Stacks service has reached [End Of Life](https://docs.aws.am
 
 * The `etag` argument is now computed only.
 
+## resource/aws_instance
+
+* The `user_data` attribute removes hashing and now displays in cleartext. Consider [AWS Best Practices](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) when using this attribute. Additionally, for base64 encoded user data, use the `user_data_base64` attribute.
path: website/docs/guides/version-6-upgrade.html.markdown
user: login: YakDriver
body: ```suggestion
* The `user_data` attribute no longer applies hashing and is now stored in clear text. **Do not include passwords or sensitive information** in `user_data`, as it will be visible in plaintext. Follow [AWS Best Practices](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) to secure your instance metadata. If you need to provide base64-encoded user data, use the `user_data_base64` attribute instead.
```

We gotta be very clear about this.
created_at: 2025-04-02T21:14:23Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-01T20:29:03Z
body: ### Community Guidelines

This comment is added to every new Pull Request to provide quick reference to how the Terraform AWS Provider is maintained. Please review the information below, and thank you for contributing to the community that keeps the provider thriving! :rocket:

#### Voting for Prioritization

* Please vote on this Pull Request by adding a :+1: [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original post to help the community and maintainers prioritize it.
* Please see our [prioritization guide](https://hashicorp.github.io/terraform-provider-aws/prioritization/) for additional information on how the maintainers handle prioritization.
* Please **do not** leave +1 or other comments that do not add relevant new information or questions; they generate extra noise for others following the Pull Request and do not help prioritize the request.

### Pull Request Authors

* Review the [contribution guide](https://hashicorp.github.io/terraform-provider-aws/) relating to the type of change you are making to ensure all of the necessary steps have been taken.
* Whether or not the branch has been rebased will **not** impact prioritization, but doing so is always a welcome surprise.


author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-02T22:00:54Z
body: > [!WARNING]
> This Issue has been closed, meaning that any additional comments are much easier for the maintainers to miss. Please assume that the maintainers will not see them.
>
> Ongoing conversations amongst community members are welcome, however, the issue will be locked after 30 days. Moving conversations to another venue, such as the [AWS Provider forum](https://discuss.hashicorp.com/c/terraform-providers/tf-aws/33), is recommended. If you have additional concerns, please open a new issue, referencing this one where needed.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-03T02:20:44Z
body: I'm going to lock this pull request because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.
author_association: CONTRIBUTOR