diff --git a/aws/data_source_aws_instance.go b/aws/data_source_aws_instance.go index 826322a6503..3956607ee05 100644 --- a/aws/data_source_aws_instance.go +++ b/aws/data_source_aws_instance.go @@ -316,6 +316,18 @@ func dataSourceAwsInstance() *schema.Resource { Type: schema.TypeBool, Computed: true, }, + "enclave_options": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, }, } } @@ -535,5 +547,9 @@ func instanceDescriptionAttributes(d *schema.ResourceData, instance *ec2.Instanc return fmt.Errorf("error setting metadata_options: %s", err) } + if err := d.Set("enclave_options", flattenEc2EnclaveOptions(instance.EnclaveOptions)); err != nil { + return fmt.Errorf("error setting enclave_options: %s", err) + } + return nil } diff --git a/aws/data_source_aws_instance_test.go b/aws/data_source_aws_instance_test.go index e8c56d7caaa..4766234a6aa 100644 --- a/aws/data_source_aws_instance_test.go +++ b/aws/data_source_aws_instance_test.go @@ -489,6 +489,26 @@ func TestAccAWSInstanceDataSource_metadataOptions(t *testing.T) { }) } +func TestAccAWSInstanceDataSource_enclaveOptions(t *testing.T) { + resourceName := "aws_instance.test" + datasourceName := "data.aws_instance.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccInstanceDataSourceConfig_enclaveOptions(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, "enclave_options.#", resourceName, "enclave_options.#"), + resource.TestCheckResourceAttrPair(datasourceName, "enclave_options.0.enabled", resourceName, "enclave_options.0.enabled"), + ), + }, + }, + }) +} + // Lookup based on InstanceID var testAccInstanceDataSourceConfig = testAccLatestAmazonLinuxHvmEbsAmiConfig() + ` resource "aws_instance" "test" { @@ -920,3 +940,29 @@ data "aws_instance" "test" { } `, rName)) } + +func testAccInstanceDataSourceConfig_enclaveOptions(rName string) string { + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(rName, false), + testAccAvailableEc2InstanceTypeForRegion("c5a.xlarge", "c5.xlarge"), + fmt.Sprintf(` +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + subnet_id = aws_subnet.test.id + + tags = { + Name = %[1]q + } + + enclave_options { + enabled = true + } +} + +data "aws_instance" "test" { + instance_id = aws_instance.test.id +} +`, rName)) +} diff --git a/aws/data_source_aws_launch_template.go b/aws/data_source_aws_launch_template.go index 3c4f27dad4e..035bda8d891 100644 --- a/aws/data_source_aws_launch_template.go +++ b/aws/data_source_aws_launch_template.go @@ -222,6 +222,18 @@ func dataSourceAwsLaunchTemplate() *schema.Resource { }, }, }, + "enclave_options": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, "monitoring": { Type: schema.TypeList, Computed: true, @@ -498,6 +510,10 @@ func dataSourceAwsLaunchTemplateRead(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("error setting metadata_options: %w", err) } + if err := d.Set("enclave_options", getEnclaveOptions(ltData.EnclaveOptions)); err != nil { + return fmt.Errorf("error setting enclave_options: %w", err) + } + if err := d.Set("monitoring", getMonitoring(ltData.Monitoring)); err != nil { return fmt.Errorf("error setting monitoring: %w", err) } diff --git a/aws/data_source_aws_launch_template_test.go b/aws/data_source_aws_launch_template_test.go index 596695bbc6e..8e68a28b792 100644 --- a/aws/data_source_aws_launch_template_test.go +++ b/aws/data_source_aws_launch_template_test.go @@ -125,6 +125,27 @@ func TestAccAWSLaunchTemplateDataSource_metadataOptions(t *testing.T) { }) } +func TestAccAWSLaunchTemplateDataSource_enclaveOptions(t *testing.T) { + rName := acctest.RandomWithPrefix("tf-acc-test") + dataSourceName := "data.aws_launch_template.test" + resourceName := "aws_launch_template.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLaunchTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLaunchTemplateDataSourceConfig_enclaveOptions(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "enclave_options.#", resourceName, "enclave_options.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "enclave_options.0.enabled", resourceName, "enclave_options.0.enabled"), + ), + }, + }, + }) +} + func TestAccAWSLaunchTemplateDataSource_associatePublicIPAddress(t *testing.T) { rName := acctest.RandomWithPrefix("tf-acc-test") dataSourceName := "data.aws_launch_template.test" @@ -274,6 +295,22 @@ data "aws_launch_template" "test" { `, rName) } +func testAccAWSLaunchTemplateDataSourceConfig_enclaveOptions(rName string) string { + return fmt.Sprintf(` +resource "aws_launch_template" "test" { + name = %[1]q + + enclave_options { + enabled = true + } +} + +data "aws_launch_template" "test" { + name = aws_launch_template.test.name +} +`, rName) +} + func testAccAWSLaunchTemplateDataSourceConfig_associatePublicIpAddress(rName, associatePublicIPAddress string) string { return fmt.Sprintf(` resource "aws_launch_template" "test" { diff --git a/aws/resource_aws_instance.go b/aws/resource_aws_instance.go index cd615990047..00f8254ed48 100644 --- a/aws/resource_aws_instance.go +++ b/aws/resource_aws_instance.go @@ -576,6 +576,23 @@ func resourceAwsInstance() *schema.Resource { }, }, }, + + "enclave_options": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + }, + }, }, } } @@ -625,6 +642,7 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { CpuOptions: instanceOpts.CpuOptions, HibernationOptions: instanceOpts.HibernationOptions, MetadataOptions: instanceOpts.MetadataOptions, + EnclaveOptions: instanceOpts.EnclaveOptions, TagSpecifications: tagSpecifications, } @@ -780,6 +798,10 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting metadata_options: %s", err) } + if err := d.Set("enclave_options", flattenEc2EnclaveOptions(instance.EnclaveOptions)); err != nil { + return fmt.Errorf("error setting enclave_options: %s", err) + } + d.Set("ami", instance.ImageId) d.Set("instance_type", instance.InstanceType) d.Set("key_name", instance.KeyName) @@ -2150,6 +2172,7 @@ type awsInstanceOpts struct { CpuOptions *ec2.CpuOptionsRequest HibernationOptions *ec2.HibernationOptionsRequest MetadataOptions *ec2.InstanceMetadataOptionsRequest + EnclaveOptions *ec2.EnclaveOptionsRequest } func buildAwsInstanceOpts(d *schema.ResourceData, meta interface{}) (*awsInstanceOpts, error) { @@ -2162,6 +2185,7 @@ func buildAwsInstanceOpts(d *schema.ResourceData, meta interface{}) (*awsInstanc ImageID: aws.String(d.Get("ami").(string)), InstanceType: aws.String(instanceType), MetadataOptions: expandEc2InstanceMetadataOptions(d.Get("metadata_options").([]interface{})), + EnclaveOptions: expandEc2EnclaveOptions(d.Get("enclave_options").([]interface{})), } // Set default cpu_credits as Unlimited for T3 instance type @@ -2465,6 +2489,20 @@ func expandEc2InstanceMetadataOptions(l []interface{}) *ec2.InstanceMetadataOpti return opts } +func expandEc2EnclaveOptions(l []interface{}) *ec2.EnclaveOptionsRequest { + if len(l) == 0 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + opts := &ec2.EnclaveOptionsRequest{ + Enabled: aws.Bool(m["enabled"].(bool)), + } + + return opts +} + //Expands an array of secondary Private IPs into a ec2 Private IP Address Spec func expandSecondaryPrivateIPAddresses(ips []interface{}) []*ec2.PrivateIpAddressSpecification { specs := make([]*ec2.PrivateIpAddressSpecification, 0, len(ips)) @@ -2492,6 +2530,18 @@ func flattenEc2InstanceMetadataOptions(opts *ec2.InstanceMetadataOptionsResponse return []interface{}{m} } +func flattenEc2EnclaveOptions(opts *ec2.EnclaveOptions) []interface{} { + if opts == nil { + return nil + } + + m := map[string]interface{}{ + "enabled": aws.BoolValue(opts.Enabled), + } + + return []interface{}{m} +} + // resourceAwsInstanceFindByID returns the EC2 instance by ID // * If the instance is found, returns the instance and nil // * If no instance is found, returns nil and nil diff --git a/aws/resource_aws_instance_test.go b/aws/resource_aws_instance_test.go index 950ea6f955c..6bb66cd830c 100644 --- a/aws/resource_aws_instance_test.go +++ b/aws/resource_aws_instance_test.go @@ -3110,6 +3110,41 @@ func TestAccAWSInstance_metadataOptions(t *testing.T) { }) } +func TestAccAWSInstance_enclaveOptions(t *testing.T) { + var instance1, instance2 ec2.Instance + resourceName := "aws_instance.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfigEnclaveOptions(true), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &instance1), + resource.TestCheckResourceAttr(resourceName, "enclave_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enclave_options.0.enabled", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceConfigEnclaveOptions(false), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists(resourceName, &instance2), + testAccCheckInstanceRecreated(&instance1, &instance2), + resource.TestCheckResourceAttr(resourceName, "enclave_options.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enclave_options.0.enabled", "false"), + ), + }, + }, + }) +} + func testAccCheckInstanceNotRecreated(t *testing.T, before, after *ec2.Instance) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -5102,6 +5137,29 @@ resource "aws_instance" "test" { `, rName)) } +func testAccInstanceConfigEnclaveOptions(enabled bool) string { + name := "tf-acc-instance-enclaves" + return composeConfig( + testAccLatestAmazonLinuxHvmEbsAmiConfig(), + testAccAwsInstanceVpcConfig(name, false), + testAccAvailableEc2InstanceTypeForRegion("c5a.xlarge", "c5.xlarge"), + fmt.Sprintf(` +resource "aws_instance" "test" { + ami = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + instance_type = data.aws_ec2_instance_type_offering.available.instance_type + subnet_id = aws_subnet.test.id + + enclave_options { + enabled = %[2]t + } + + tags = { + Name = %[1]q + } +} +`, name, enabled)) +} + func testAccAwsEc2InstanceConfigDynamicEBSBlockDevices() string { return composeConfig(testAccLatestAmazonLinuxPvEbsAmiConfig(), ` resource "aws_instance" "test" { diff --git a/aws/resource_aws_launch_template.go b/aws/resource_aws_launch_template.go index 9b41755ab3c..44bc7d0615e 100644 --- a/aws/resource_aws_launch_template.go +++ b/aws/resource_aws_launch_template.go @@ -399,6 +399,20 @@ func resourceAwsLaunchTemplate() *schema.Resource { }, }, + "enclave_options": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + "monitoring": { Type: schema.TypeList, Optional: true, @@ -791,6 +805,10 @@ func resourceAwsLaunchTemplateRead(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("error setting metadata_options: %s", err) } + if err := d.Set("enclave_options", getEnclaveOptions(ltData.EnclaveOptions)); err != nil { + return fmt.Errorf("error setting enclave_options: %s", err) + } + if err := d.Set("monitoring", getMonitoring(ltData.Monitoring)); err != nil { return fmt.Errorf("error setting monitoring: %s", err) } @@ -1105,6 +1123,17 @@ func flattenLaunchTemplateInstanceMetadataOptions(opts *ec2.LaunchTemplateInstan return []interface{}{m} } +func getEnclaveOptions(m *ec2.LaunchTemplateEnclaveOptions) []interface{} { + s := []interface{}{} + if m != nil { + mo := map[string]interface{}{ + "enabled": aws.BoolValue(m.Enabled), + } + s = append(s, mo) + } + return s +} + func getMonitoring(m *ec2.LaunchTemplatesMonitoring) []interface{} { s := []interface{}{} if m != nil { @@ -1373,6 +1402,17 @@ func buildLaunchTemplateData(d *schema.ResourceData) (*ec2.RequestLaunchTemplate opts.MetadataOptions = expandLaunchTemplateInstanceMetadataOptions(v.([]interface{})) } + if v, ok := d.GetOk("enclave_options"); ok { + m := v.([]interface{}) + if len(m) > 0 && m[0] != nil { + mData := m[0].(map[string]interface{}) + enclaveOptions := &ec2.LaunchTemplateEnclaveOptionsRequest{ + Enabled: aws.Bool(mData["enabled"].(bool)), + } + opts.EnclaveOptions = enclaveOptions + } + } + if v, ok := d.GetOk("monitoring"); ok { m := v.([]interface{}) if len(m) > 0 && m[0] != nil { @@ -1784,6 +1824,7 @@ var updateKeys = []string{ "ebs_optimized", "elastic_gpu_specifications", "elastic_inference_accelerator", + "enclave_options", "hibernation_options", "iam_instance_profile", "image_id", diff --git a/aws/resource_aws_launch_template_test.go b/aws/resource_aws_launch_template_test.go index abe1c477cc1..373a455d3da 100644 --- a/aws/resource_aws_launch_template_test.go +++ b/aws/resource_aws_launch_template_test.go @@ -957,6 +957,46 @@ func TestAccAWSLaunchTemplate_metadataOptions(t *testing.T) { }) } +func TestAccAWSLaunchTemplate_enclaveOptions(t *testing.T) { + var template ec2.LaunchTemplate + resourceName := "aws_launch_template.test" + rName := acctest.RandomWithPrefix("tf-acc-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLaunchTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLaunchTemplateConfig_enclaveOptions(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLaunchTemplateExists(resourceName, &template), + resource.TestCheckResourceAttr(resourceName, "enclave_options.0.enabled", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSLaunchTemplateConfig_enclaveOptions(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLaunchTemplateExists(resourceName, &template), + resource.TestCheckResourceAttr(resourceName, "enclave_options.0.enabled", "false"), + ), + }, + { + Config: testAccAWSLaunchTemplateConfig_enclaveOptions(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLaunchTemplateExists(resourceName, &template), + resource.TestCheckResourceAttr(resourceName, "enclave_options.0.enabled", "true"), + ), + }, + }, + }) +} + func TestAccAWSLaunchTemplate_hibernation(t *testing.T) { var template ec2.LaunchTemplate resourceName := "aws_launch_template.test" @@ -1807,6 +1847,18 @@ resource "aws_launch_template" "test" { `, rName) } +func testAccAWSLaunchTemplateConfig_enclaveOptions(rName string, enabled bool) string { + return fmt.Sprintf(` +resource "aws_launch_template" "test" { + name = %[1]q + + enclave_options { + enabled = %[2]t + } +} +`, rName, enabled) +} + func testAccAWSLaunchTemplateConfigHibernation(rName string, enabled bool) string { return fmt.Sprintf(` resource "aws_launch_template" "test" { diff --git a/website/docs/d/instance.html.markdown b/website/docs/d/instance.html.markdown index 83dcb7f3880..8753f953a4c 100644 --- a/website/docs/d/instance.html.markdown +++ b/website/docs/d/instance.html.markdown @@ -119,5 +119,7 @@ interpolation. * `http_endpoint` - The state of the metadata service: `enabled`, `disabled`. * `http_tokens` - If session tokens are required: `optional`, `required`. * `http_put_response_hop_limit` - The desired HTTP PUT response hop limit for instance metadata requests. +* `enclave_options` - The enclave options of the Instance. + * `enabled` - Whether Nitro Enclaves are enabled. [1]: http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instances.html diff --git a/website/docs/d/launch_template.html.markdown b/website/docs/d/launch_template.html.markdown index f9b73bf511e..9337c6d29eb 100644 --- a/website/docs/d/launch_template.html.markdown +++ b/website/docs/d/launch_template.html.markdown @@ -87,4 +87,6 @@ In addition to all arguments above, the following attributes are exported: * `tags` - (Optional) A map of tags to assign to the launch template. * `user_data` - The Base64-encoded user data to provide when launching the instance. * `hibernation_options` - The hibernation options for the instance. +* `enclave_options` - The enclave options of the Instance. + * `enabled` - Whether Nitro Enclaves are enabled. diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index 55d6a9498df..ad1e5d7ba8c 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -108,6 +108,7 @@ instances. See [Shutdown Behavior](https://docs.aws.amazon.com/AWSEC2/latest/Use * `credit_specification` - (Optional) Customize the credit specification of the instance. See [Credit Specification](#credit-specification) below for more details. * `hibernation` - (Optional) If true, the launched EC2 instance will support hibernation. * `metadata_options` - (Optional) Customize the metadata options of the instance. See [Metadata Options](#metadata-options) below for more details. +* `enclave_options` - (Optional) Enable Nitro Enclaves on launched instances. See [Enclave Options](#enclave-options) below for more details. ### Timeouts @@ -211,6 +212,18 @@ The `metadata_options` block supports the following: For more information, see the documentation on the [Instance Metadata Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html). +### Enclave Options + +-> **NOTE:** Changing `enabled` will cause the resource to be destroyed and re-created. + +Enclave options apply to the instance at boot time. + +The `enclave_options` block supports the following: + +* `enabled` - (Optional) Whether Nitro Enclaves will be enabled on the instance. (Default: `"false"`). + +For more information, see the documentation on [Nitro Enclaves](https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html). + ### Example ```hcl diff --git a/website/docs/r/launch_template.html.markdown b/website/docs/r/launch_template.html.markdown index 6adf5f4c5ac..80e2e44d0a6 100644 --- a/website/docs/r/launch_template.html.markdown +++ b/website/docs/r/launch_template.html.markdown @@ -150,6 +150,7 @@ The following arguments are supported: * `tags` - (Optional) A map of tags to assign to the launch template. * `user_data` - The Base64-encoded user data to provide when launching the instance. * `hibernation_options` - The hibernation options for the instance. See [Hibernation Options](#hibernation-options) below for more details. +* `enclave_options` - (Optional) Enable Nitro Enclaves on launched instances. See [Enclave Options](#enclave-options) below for more details. ### Block devices @@ -322,6 +323,14 @@ The `hibernation_options` block supports the following: * `configured` - If set to `true`, the launched EC2 instance will hibernation enabled. +### Enclave Options + +The `enclave_options` block supports the following: + +* `enabled` - If set to `true`, Nitro Enclaves will be enabled on the instance. + +For more information, see the documentation on [Nitro Enclaves](https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave.html). + ### Tag Specifications The tags to apply to the resources during launch. You can tag instances, volumes, elastic GPUs and spot instance requests. More information can be found in the [EC2 API documentation](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_LaunchTemplateTagSpecificationRequest.html).