From 94ebc9ccebd9177ca5baa9add3b043bdec58cb5d Mon Sep 17 00:00:00 2001 From: Bharath Vedartham Date: Fri, 4 Dec 2020 18:57:02 +0530 Subject: [PATCH] Add support for aws ec2 instance metadata v2 A new field is add to the InstanceGroup spec with 2 sub fields, HTTPPutResponseHopLimit and HTTPTokens. These fields enable the user to disable IMDv1 for instances within an instance group. By default, both IMDv1 and IMDv2 are enabled in instances in an instance group. --- cloudmock/aws/mockec2/launch_templates.go | 6 +++ k8s/crds/kops.k8s.io_instancegroups.yaml | 11 ++++ pkg/apis/kops/instancegroup.go | 12 +++++ pkg/apis/kops/v1alpha2/instancegroup.go | 12 +++++ .../kops/v1alpha2/zz_generated.conversion.go | 50 +++++++++++++++++++ .../kops/v1alpha2/zz_generated.deepcopy.go | 31 ++++++++++++ pkg/apis/kops/validation/aws.go | 22 ++++++++ pkg/apis/kops/validation/aws_test.go | 45 +++++++++++++++++ pkg/apis/kops/zz_generated.deepcopy.go | 31 ++++++++++++ pkg/model/awsmodel/autoscalinggroup.go | 47 ++++++++++------- .../cloudup/awstasks/launchconfiguration.go | 9 ++++ .../pkg/fi/cloudup/awstasks/launchtemplate.go | 4 ++ .../awstasks/launchtemplate_target_api.go | 9 ++++ .../launchtemplate_target_cloudformation.go | 13 +++++ ...unchtemplate_target_cloudformation_test.go | 16 +++++- .../launchtemplate_target_terraform.go | 13 +++++ .../launchtemplate_target_terraform_test.go | 16 +++++- 17 files changed, 325 insertions(+), 22 deletions(-) diff --git a/cloudmock/aws/mockec2/launch_templates.go b/cloudmock/aws/mockec2/launch_templates.go index 51cb0aee91375..52a5aa844c060 100644 --- a/cloudmock/aws/mockec2/launch_templates.go +++ b/cloudmock/aws/mockec2/launch_templates.go @@ -147,6 +147,12 @@ func (m *MockEC2) CreateLaunchTemplate(request *ec2.CreateLaunchTemplateInput) ( name: request.LaunchTemplateName, } + if request.LaunchTemplateData.MetadataOptions != nil { + resp.MetadataOptions = &ec2.LaunchTemplateInstanceMetadataOptions{ + HttpTokens: request.LaunchTemplateData.MetadataOptions.HttpTokens, + HttpPutResponseHopLimit: request.LaunchTemplateData.MetadataOptions.HttpPutResponseHopLimit, + } + } if request.LaunchTemplateData.Monitoring != nil { resp.Monitoring = &ec2.LaunchTemplatesMonitoring{Enabled: request.LaunchTemplateData.Monitoring.Enabled} } diff --git a/k8s/crds/kops.k8s.io_instancegroups.yaml b/k8s/crds/kops.k8s.io_instancegroups.yaml index aa3b9c9b9f9a8..e6578a8a12aca 100644 --- a/k8s/crds/kops.k8s.io_instancegroups.yaml +++ b/k8s/crds/kops.k8s.io_instancegroups.yaml @@ -190,6 +190,17 @@ spec: instanceInterruptionBehavior: description: InstanceInterruptionBehavior defines if a spot instance should be terminated, hibernated, or stopped after interruption type: string + instanceMetadata: + description: InstanceMetadata defines the EC2 instance metadata service options (AWS Only) + properties: + httpPutResponseHopLimit: + description: HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests. The larger the number, the further instance metadata requests can travel. The default value is 1. + format: int64 + type: integer + httpTokens: + description: HTTPTokens is the state of token usage for the instance metadata requests. If the parameter is not specified in the request, the default state is "optional". + type: string + type: object instanceProtection: description: InstanceProtection makes new instances in an autoscaling group protected from scale in type: boolean diff --git a/pkg/apis/kops/instancegroup.go b/pkg/apis/kops/instancegroup.go index eb92dc12fe9f1..a72c026fd975a 100644 --- a/pkg/apis/kops/instancegroup.go +++ b/pkg/apis/kops/instancegroup.go @@ -164,6 +164,8 @@ type InstanceGroupSpec struct { // InstanceInterruptionBehavior defines if a spot instance should be terminated, hibernated, // or stopped after interruption InstanceInterruptionBehavior *string `json:"instanceInterruptionBehavior,omitempty"` + // InstanceMetadata defines the EC2 instance metadata service options (AWS Only) + InstanceMetadata *InstanceMetadataOptions `json:"instanceMetadata,omitempty"` } const ( @@ -178,6 +180,16 @@ const ( // SpotAllocationStrategies is a collection of supported strategies var SpotAllocationStrategies = []string{SpotAllocationStrategyLowestPrices, SpotAllocationStrategyDiversified, SpotAllocationStrategyCapacityOptimized} +// InstanceMetadata defines the EC2 instance metadata service options (AWS Only) +type InstanceMetadataOptions struct { + // HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests. + // The larger the number, the further instance metadata requests can travel. The default value is 1. + HTTPPutResponseHopLimit *int64 `json:"httpPutResponseHopLimit,omitempty"` + // HTTPTokens is the state of token usage for the instance metadata requests. + // If the parameter is not specified in the request, the default state is "optional". + HTTPTokens *string `json:"httpTokens,omitempty"` +} + // MixedInstancesPolicySpec defines the specification for an autoscaling group backed by a ec2 fleet type MixedInstancesPolicySpec struct { // Instances is a list of instance types which we are willing to run in the EC2 fleet diff --git a/pkg/apis/kops/v1alpha2/instancegroup.go b/pkg/apis/kops/v1alpha2/instancegroup.go index 1a8ffb27a3a89..0b18f00832f20 100644 --- a/pkg/apis/kops/v1alpha2/instancegroup.go +++ b/pkg/apis/kops/v1alpha2/instancegroup.go @@ -162,6 +162,8 @@ type InstanceGroupSpec struct { // InstanceInterruptionBehavior defines if a spot instance should be terminated, hibernated, // or stopped after interruption InstanceInterruptionBehavior *string `json:"instanceInterruptionBehavior,omitempty"` + // InstanceMetadata defines the EC2 instance metadata service options (AWS Only) + InstanceMetadata *InstanceMetadataOptions `json:"instanceMetadata,omitempty"` } const ( @@ -176,6 +178,16 @@ const ( // SpotAllocationStrategies is a collection of supported strategies var SpotAllocationStrategies = []string{SpotAllocationStrategyLowestPrices, SpotAllocationStrategyDiversified, SpotAllocationStrategyCapacityOptimized} +// InstanceMetadata defines the EC2 instance metadata service options (AWS Only) +type InstanceMetadataOptions struct { + // HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests. + // The larger the number, the further instance metadata requests can travel. The default value is 1. + HTTPPutResponseHopLimit *int64 `json:"httpPutResponseHopLimit,omitempty"` + // HTTPTokens is the state of token usage for the instance metadata requests. + // If the parameter is not specified in the request, the default state is "optional". + HTTPTokens *string `json:"httpTokens,omitempty"` +} + // MixedInstancesPolicySpec defines the specification for an autoscaling group backed by a ec2 fleet type MixedInstancesPolicySpec struct { // Instances is a list of instance types which we are willing to run in the EC2 fleet diff --git a/pkg/apis/kops/v1alpha2/zz_generated.conversion.go b/pkg/apis/kops/v1alpha2/zz_generated.conversion.go index d915a11dd8ba4..6216ec9ddf1d0 100644 --- a/pkg/apis/kops/v1alpha2/zz_generated.conversion.go +++ b/pkg/apis/kops/v1alpha2/zz_generated.conversion.go @@ -513,6 +513,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*InstanceMetadataOptions)(nil), (*kops.InstanceMetadataOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_InstanceMetadataOptions_To_kops_InstanceMetadataOptions(a.(*InstanceMetadataOptions), b.(*kops.InstanceMetadataOptions), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*kops.InstanceMetadataOptions)(nil), (*InstanceMetadataOptions)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_kops_InstanceMetadataOptions_To_v1alpha2_InstanceMetadataOptions(a.(*kops.InstanceMetadataOptions), b.(*InstanceMetadataOptions), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*Keyset)(nil), (*kops.Keyset)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha2_Keyset_To_kops_Keyset(a.(*Keyset), b.(*kops.Keyset), scope) }); err != nil { @@ -3570,6 +3580,15 @@ func autoConvert_v1alpha2_InstanceGroupSpec_To_kops_InstanceGroupSpec(in *Instan out.RollingUpdate = nil } out.InstanceInterruptionBehavior = in.InstanceInterruptionBehavior + if in.InstanceMetadata != nil { + in, out := &in.InstanceMetadata, &out.InstanceMetadata + *out = new(kops.InstanceMetadataOptions) + if err := Convert_v1alpha2_InstanceMetadataOptions_To_kops_InstanceMetadataOptions(*in, *out, s); err != nil { + return err + } + } else { + out.InstanceMetadata = nil + } return nil } @@ -3708,6 +3727,15 @@ func autoConvert_kops_InstanceGroupSpec_To_v1alpha2_InstanceGroupSpec(in *kops.I out.RollingUpdate = nil } out.InstanceInterruptionBehavior = in.InstanceInterruptionBehavior + if in.InstanceMetadata != nil { + in, out := &in.InstanceMetadata, &out.InstanceMetadata + *out = new(InstanceMetadataOptions) + if err := Convert_kops_InstanceMetadataOptions_To_v1alpha2_InstanceMetadataOptions(*in, *out, s); err != nil { + return err + } + } else { + out.InstanceMetadata = nil + } return nil } @@ -3716,6 +3744,28 @@ func Convert_kops_InstanceGroupSpec_To_v1alpha2_InstanceGroupSpec(in *kops.Insta return autoConvert_kops_InstanceGroupSpec_To_v1alpha2_InstanceGroupSpec(in, out, s) } +func autoConvert_v1alpha2_InstanceMetadataOptions_To_kops_InstanceMetadataOptions(in *InstanceMetadataOptions, out *kops.InstanceMetadataOptions, s conversion.Scope) error { + out.HTTPPutResponseHopLimit = in.HTTPPutResponseHopLimit + out.HTTPTokens = in.HTTPTokens + return nil +} + +// Convert_v1alpha2_InstanceMetadataOptions_To_kops_InstanceMetadataOptions is an autogenerated conversion function. +func Convert_v1alpha2_InstanceMetadataOptions_To_kops_InstanceMetadataOptions(in *InstanceMetadataOptions, out *kops.InstanceMetadataOptions, s conversion.Scope) error { + return autoConvert_v1alpha2_InstanceMetadataOptions_To_kops_InstanceMetadataOptions(in, out, s) +} + +func autoConvert_kops_InstanceMetadataOptions_To_v1alpha2_InstanceMetadataOptions(in *kops.InstanceMetadataOptions, out *InstanceMetadataOptions, s conversion.Scope) error { + out.HTTPPutResponseHopLimit = in.HTTPPutResponseHopLimit + out.HTTPTokens = in.HTTPTokens + return nil +} + +// Convert_kops_InstanceMetadataOptions_To_v1alpha2_InstanceMetadataOptions is an autogenerated conversion function. +func Convert_kops_InstanceMetadataOptions_To_v1alpha2_InstanceMetadataOptions(in *kops.InstanceMetadataOptions, out *InstanceMetadataOptions, s conversion.Scope) error { + return autoConvert_kops_InstanceMetadataOptions_To_v1alpha2_InstanceMetadataOptions(in, out, s) +} + func autoConvert_v1alpha2_Keyset_To_kops_Keyset(in *Keyset, out *kops.Keyset, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1alpha2_KeysetSpec_To_kops_KeysetSpec(&in.Spec, &out.Spec, s); err != nil { diff --git a/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go index f25432210da5b..db561da6b6d2b 100644 --- a/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go @@ -1918,6 +1918,11 @@ func (in *InstanceGroupSpec) DeepCopyInto(out *InstanceGroupSpec) { *out = new(string) **out = **in } + if in.InstanceMetadata != nil { + in, out := &in.InstanceMetadata, &out.InstanceMetadata + *out = new(InstanceMetadataOptions) + (*in).DeepCopyInto(*out) + } return } @@ -1931,6 +1936,32 @@ func (in *InstanceGroupSpec) DeepCopy() *InstanceGroupSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstanceMetadataOptions) DeepCopyInto(out *InstanceMetadataOptions) { + *out = *in + if in.HTTPPutResponseHopLimit != nil { + in, out := &in.HTTPPutResponseHopLimit, &out.HTTPPutResponseHopLimit + *out = new(int64) + **out = **in + } + if in.HTTPTokens != nil { + in, out := &in.HTTPTokens, &out.HTTPTokens + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceMetadataOptions. +func (in *InstanceMetadataOptions) DeepCopy() *InstanceMetadataOptions { + if in == nil { + return nil + } + out := new(InstanceMetadataOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Keyset) DeepCopyInto(out *Keyset) { *out = *in diff --git a/pkg/apis/kops/validation/aws.go b/pkg/apis/kops/validation/aws.go index 82837412055f5..dd0a30713b2ff 100644 --- a/pkg/apis/kops/validation/aws.go +++ b/pkg/apis/kops/validation/aws.go @@ -56,6 +56,28 @@ func awsValidateInstanceGroup(ig *kops.InstanceGroup, cloud awsup.AWSCloud) fiel allErrs = append(allErrs, awsValidateMixedInstancesPolicy(field.NewPath("spec", "mixedInstancesPolicy"), ig.Spec.MixedInstancesPolicy, ig, cloud)...) } + if ig.Spec.InstanceMetadata != nil { + allErrs = append(allErrs, awsValidateInstanceMetadata(field.NewPath("spec", "instanceMetadata"), ig.Spec.InstanceMetadata)...) + } + + return allErrs +} + +func awsValidateInstanceMetadata(fieldPath *field.Path, instanceMetadata *kops.InstanceMetadataOptions) field.ErrorList { + allErrs := field.ErrorList{} + + if instanceMetadata.HTTPTokens != nil { + allErrs = append(allErrs, IsValidValue(fieldPath.Child("httpTokens"), instanceMetadata.HTTPTokens, []string{"optional", "required"})...) + } + + if instanceMetadata.HTTPPutResponseHopLimit != nil { + httpPutResponseHopLimit := fi.Int64Value(instanceMetadata.HTTPPutResponseHopLimit) + if httpPutResponseHopLimit < 1 || httpPutResponseHopLimit > 64 { + allErrs = append(allErrs, field.Invalid(fieldPath.Child("httpPutResponseHopLimit"), instanceMetadata.HTTPPutResponseHopLimit, + "HTTPPutResponseLimit must be a value between 1 and 64")) + } + } + return allErrs } diff --git a/pkg/apis/kops/validation/aws_test.go b/pkg/apis/kops/validation/aws_test.go index cc7cc97abdb56..b0205f5f05183 100644 --- a/pkg/apis/kops/validation/aws_test.go +++ b/pkg/apis/kops/validation/aws_test.go @@ -157,3 +157,48 @@ func TestValidateInstanceGroupSpec(t *testing.T) { testErrors(t, g.Input, errs, g.ExpectedErrors) } } + +func TestInstanceMetadataOptions(t *testing.T) { + cloud := awsup.BuildMockAWSCloud("us-east-1", "abc") + + tests := []struct { + ig *kops.InstanceGroup + expected []string + }{ + { + ig: &kops.InstanceGroup{ + ObjectMeta: v1.ObjectMeta{ + Name: "some-ig", + }, + Spec: kops.InstanceGroupSpec{ + Role: "Node", + InstanceMetadata: &kops.InstanceMetadataOptions{ + HTTPPutResponseHopLimit: fi.Int64(1), + HTTPTokens: fi.String("abc"), + }, + }, + }, + expected: []string{"Unsupported value::spec.instanceMetadata.httpTokens"}, + }, + { + ig: &kops.InstanceGroup{ + ObjectMeta: v1.ObjectMeta{ + Name: "some-ig", + }, + Spec: kops.InstanceGroupSpec{ + Role: "Node", + InstanceMetadata: &kops.InstanceMetadataOptions{ + HTTPPutResponseHopLimit: fi.Int64(-1), + HTTPTokens: fi.String("required"), + }, + }, + }, + expected: []string{"Invalid value::spec.instanceMetadata.httpPutResponseHopLimit"}, + }, + } + + for _, test := range tests { + errs := ValidateInstanceGroup(test.ig, cloud) + testErrors(t, test.ig.ObjectMeta.Name, errs, test.expected) + } +} diff --git a/pkg/apis/kops/zz_generated.deepcopy.go b/pkg/apis/kops/zz_generated.deepcopy.go index 03894625b9bfb..94384b37436e8 100644 --- a/pkg/apis/kops/zz_generated.deepcopy.go +++ b/pkg/apis/kops/zz_generated.deepcopy.go @@ -2084,6 +2084,11 @@ func (in *InstanceGroupSpec) DeepCopyInto(out *InstanceGroupSpec) { *out = new(string) **out = **in } + if in.InstanceMetadata != nil { + in, out := &in.InstanceMetadata, &out.InstanceMetadata + *out = new(InstanceMetadataOptions) + (*in).DeepCopyInto(*out) + } return } @@ -2097,6 +2102,32 @@ func (in *InstanceGroupSpec) DeepCopy() *InstanceGroupSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InstanceMetadataOptions) DeepCopyInto(out *InstanceMetadataOptions) { + *out = *in + if in.HTTPPutResponseHopLimit != nil { + in, out := &in.HTTPPutResponseHopLimit, &out.HTTPPutResponseHopLimit + *out = new(int64) + **out = **in + } + if in.HTTPTokens != nil { + in, out := &in.HTTPTokens, &out.HTTPTokens + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceMetadataOptions. +func (in *InstanceMetadataOptions) DeepCopy() *InstanceMetadataOptions { + if in == nil { + return nil + } + out := new(InstanceMetadataOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Keyset) DeepCopyInto(out *Keyset) { *out = *in diff --git a/pkg/model/awsmodel/autoscalinggroup.go b/pkg/model/awsmodel/autoscalinggroup.go index 54c324e04aeb4..f6783221da6e6 100644 --- a/pkg/model/awsmodel/autoscalinggroup.go +++ b/pkg/model/awsmodel/autoscalinggroup.go @@ -113,24 +113,26 @@ func (b *AutoscalingGroupModelBuilder) buildLaunchTemplateTask(c *fi.ModelBuilde // LaunchConfiguration as an anonymous field, bit given up the task dependency walker works this caused issues, due // to the creation of a implicit dependency lt := &awstasks.LaunchTemplate{ - Name: fi.String(name), - Lifecycle: b.Lifecycle, - AssociatePublicIP: lc.AssociatePublicIP, - BlockDeviceMappings: lc.BlockDeviceMappings, - IAMInstanceProfile: lc.IAMInstanceProfile, - ImageID: lc.ImageID, - InstanceMonitoring: lc.InstanceMonitoring, - InstanceType: lc.InstanceType, - RootVolumeOptimization: lc.RootVolumeOptimization, - RootVolumeSize: lc.RootVolumeSize, - RootVolumeIops: lc.RootVolumeIops, - RootVolumeType: lc.RootVolumeType, - RootVolumeEncryption: lc.RootVolumeEncryption, - SSHKey: lc.SSHKey, - SecurityGroups: lc.SecurityGroups, - Tags: tags, - Tenancy: lc.Tenancy, - UserData: lc.UserData, + Name: fi.String(name), + Lifecycle: b.Lifecycle, + AssociatePublicIP: lc.AssociatePublicIP, + BlockDeviceMappings: lc.BlockDeviceMappings, + IAMInstanceProfile: lc.IAMInstanceProfile, + ImageID: lc.ImageID, + InstanceMonitoring: lc.InstanceMonitoring, + InstanceType: lc.InstanceType, + RootVolumeOptimization: lc.RootVolumeOptimization, + RootVolumeSize: lc.RootVolumeSize, + RootVolumeIops: lc.RootVolumeIops, + RootVolumeType: lc.RootVolumeType, + RootVolumeEncryption: lc.RootVolumeEncryption, + SSHKey: lc.SSHKey, + SecurityGroups: lc.SecurityGroups, + Tags: tags, + Tenancy: lc.Tenancy, + UserData: lc.UserData, + HTTPTokens: lc.HTTPTokens, + HTTPPutResponseHopLimit: lc.HTTPPutResponseHopLimit, } // When using a MixedInstances ASG, AWS requires the SpotPrice be defined on the ASG // rather than the LaunchTemplate or else it returns this error: @@ -203,6 +205,15 @@ func (b *AutoscalingGroupModelBuilder) buildLaunchConfigurationTask(c *fi.ModelB SecurityGroups: []*awstasks.SecurityGroup{sgLink}, } + t.HTTPTokens = fi.String("optional") + if ig.Spec.InstanceMetadata != nil && ig.Spec.InstanceMetadata.HTTPTokens != nil { + t.HTTPTokens = ig.Spec.InstanceMetadata.HTTPTokens + } + t.HTTPPutResponseHopLimit = fi.Int64(1) + if ig.Spec.InstanceMetadata != nil && ig.Spec.InstanceMetadata.HTTPPutResponseHopLimit != nil { + t.HTTPPutResponseHopLimit = ig.Spec.InstanceMetadata.HTTPPutResponseHopLimit + } + if b.APILoadBalancerClass() == kops.LoadBalancerClassNetwork { for _, id := range b.Cluster.Spec.API.LoadBalancer.AdditionalSecurityGroups { sgTask := &awstasks.SecurityGroup{ diff --git a/upup/pkg/fi/cloudup/awstasks/launchconfiguration.go b/upup/pkg/fi/cloudup/awstasks/launchconfiguration.go index 9b29ed7a349a6..0cf14dd498d6e 100644 --- a/upup/pkg/fi/cloudup/awstasks/launchconfiguration.go +++ b/upup/pkg/fi/cloudup/awstasks/launchconfiguration.go @@ -60,6 +60,10 @@ type LaunchConfiguration struct { AssociatePublicIP *bool // BlockDeviceMappings is a block device mappings BlockDeviceMappings []*BlockDeviceMapping + // HTTPTokens is the state of token usage for your instance metadata requests. + HTTPTokens *string + // HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests. + HTTPPutResponseHopLimit *int64 // IAMInstanceProfile is the IAM profile to assign to the nodes IAMInstanceProfile *IAMInstanceProfile // ID is the launch configuration name @@ -297,6 +301,11 @@ func (_ *LaunchConfiguration) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *La LaunchConfigurationName: &launchConfigurationName, } + request.MetadataOptions = &autoscaling.InstanceMetadataOptions{ + HttpPutResponseHopLimit: e.HTTPPutResponseHopLimit, + HttpTokens: e.HTTPTokens, + } + if e.SSHKey != nil { request.KeyName = e.SSHKey.Name } diff --git a/upup/pkg/fi/cloudup/awstasks/launchtemplate.go b/upup/pkg/fi/cloudup/awstasks/launchtemplate.go index 83fde74033b1b..6c34dae6d6793 100644 --- a/upup/pkg/fi/cloudup/awstasks/launchtemplate.go +++ b/upup/pkg/fi/cloudup/awstasks/launchtemplate.go @@ -39,6 +39,10 @@ type LaunchTemplate struct { AssociatePublicIP *bool // BlockDeviceMappings is a block device mappings BlockDeviceMappings []*BlockDeviceMapping + // HTTPTokens is the state of token usage for your instance metadata requests. + HTTPTokens *string + // HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests. + HTTPPutResponseHopLimit *int64 // IAMInstanceProfile is the IAM profile to assign to the nodes IAMInstanceProfile *IAMInstanceProfile // ImageID is the AMI to use for the instances diff --git a/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_api.go b/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_api.go index 54ea2afaf4da1..45a9df6407829 100644 --- a/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_api.go +++ b/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_api.go @@ -42,6 +42,10 @@ func (t *LaunchTemplate) RenderAWS(c *awsup.AWSAPITarget, a, e, changes *LaunchT EbsOptimized: t.RootVolumeOptimization, ImageId: image.ImageId, InstanceType: t.InstanceType, + MetadataOptions: &ec2.LaunchTemplateInstanceMetadataOptionsRequest{ + HttpPutResponseHopLimit: t.HTTPPutResponseHopLimit, + HttpTokens: t.HTTPTokens, + }, NetworkInterfaces: []*ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{ { AssociatePublicIpAddress: t.AssociatePublicIP, @@ -228,6 +232,11 @@ func (t *LaunchTemplate) Find(c *fi.Context) (*LaunchTemplate, error) { if lt.LaunchTemplateData.IamInstanceProfile != nil { actual.IAMInstanceProfile = &IAMInstanceProfile{Name: lt.LaunchTemplateData.IamInstanceProfile.Name} } + // @step: add instance metadata options + if lt.LaunchTemplateData.MetadataOptions != nil { + actual.HTTPPutResponseHopLimit = lt.LaunchTemplateData.MetadataOptions.HttpPutResponseHopLimit + actual.HTTPTokens = lt.LaunchTemplateData.MetadataOptions.HttpTokens + } // @step: add InstanceMarketOptions if there are any imo := lt.LaunchTemplateData.InstanceMarketOptions if imo != nil && imo.SpotOptions != nil && aws.StringValue(imo.SpotOptions.MaxPrice) != "" { diff --git a/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_cloudformation.go b/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_cloudformation.go index 21ac2f291f322..7ce117ae6ee47 100644 --- a/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_cloudformation.go +++ b/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_cloudformation.go @@ -108,6 +108,13 @@ type cloudformationLaunchTemplateTagSpecification struct { Tags []cloudformationTag `json:"Tags,omitempty"` } +type cloudformationLaunchTemplateInstanceMetadataOptions struct { + // HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests. + HTTPPutResponseHopLimit *int64 `json:"HttpPutResponseHopLimit,omitempty"` + // HTTPTokens is the state of token usage for your instance metadata requests. + HTTPTokens *string `json:"HttpTokens,omitempty"` +} + type cloudformationLaunchTemplateData struct { // BlockDeviceMappings is the device mappings BlockDeviceMappings []*cloudformationLaunchTemplateBlockDevice `json:"BlockDeviceMappings,omitempty"` @@ -123,6 +130,8 @@ type cloudformationLaunchTemplateData struct { KeyName *string `json:"KeyName,omitempty"` // MarketOptions are the spot pricing options MarketOptions *cloudformationLaunchTemplateMarketOptions `json:"InstanceMarketOptions,omitempty"` + // MetadataOptions are the instance metadata options. + MetadataOptions *cloudformationLaunchTemplateInstanceMetadataOptions `json:"MetadataOptions,omitempty"` // Monitoring are the instance monitoring options Monitoring *cloudformationLaunchTemplateMonitoring `json:"Monitoring,omitempty"` // NetworkInterfaces are the networking options @@ -171,6 +180,10 @@ func (t *LaunchTemplate) RenderCloudformation(target *cloudformation.Cloudformat EBSOptimized: e.RootVolumeOptimization, ImageID: image, InstanceType: e.InstanceType, + MetadataOptions: &cloudformationLaunchTemplateInstanceMetadataOptions{ + HTTPTokens: e.HTTPTokens, + HTTPPutResponseHopLimit: e.HTTPPutResponseHopLimit, + }, NetworkInterfaces: []*cloudformationLaunchTemplateNetworkInterface{ { AssociatePublicIPAddress: e.AssociatePublicIP, diff --git a/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_cloudformation_test.go b/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_cloudformation_test.go index ead2b6ee48a78..503d491dd095d 100644 --- a/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_cloudformation_test.go +++ b/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_cloudformation_test.go @@ -47,7 +47,9 @@ func TestLaunchTemplateCloudformationRender(t *testing.T) { {Name: fi.String("nodes-1"), ID: fi.String("1111")}, {Name: fi.String("nodes-2"), ID: fi.String("2222")}, }, - Tenancy: fi.String("dedicated"), + Tenancy: fi.String("dedicated"), + HTTPTokens: fi.String("required"), + HTTPPutResponseHopLimit: fi.Int64(1), }, Expected: `{ "Resources": { @@ -72,6 +74,10 @@ func TestLaunchTemplateCloudformationRender(t *testing.T) { "MaxPrice": "10" } }, + "MetadataOptions": { + "HttpTokens": "required", + "HttpPutResponseHopLimit": 1 + }, "Monitoring": { "Enabled": true }, @@ -130,7 +136,9 @@ func TestLaunchTemplateCloudformationRender(t *testing.T) { {Name: fi.String("nodes-1"), ID: fi.String("1111")}, {Name: fi.String("nodes-2"), ID: fi.String("2222")}, }, - Tenancy: fi.String("dedicated"), + Tenancy: fi.String("dedicated"), + HTTPTokens: fi.String("optional"), + HTTPPutResponseHopLimit: fi.Int64(1), }, Expected: `{ "Resources": { @@ -158,6 +166,10 @@ func TestLaunchTemplateCloudformationRender(t *testing.T) { }, "InstanceType": "t2.medium", "KeyName": "mykey", + "MetadataOptions": { + "HttpTokens": "optional", + "HttpPutResponseHopLimit": 1 + }, "Monitoring": { "Enabled": true }, diff --git a/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_terraform.go b/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_terraform.go index a456a7f45097c..5fe58fa04f204 100644 --- a/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_terraform.go +++ b/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_terraform.go @@ -108,6 +108,13 @@ type terraformLaunchTemplateTagSpecification struct { Tags map[string]string `json:"tags,omitempty" cty:"tags"` } +type terraformLaunchTemplateInstanceMetadata struct { + // HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests. + HTTPPutResponseHopLimit *int64 `json:"http_put_response_hop_limit,omitempty" cty:"http_put_response_hop_limit"` + // HTTPTokens is the state of token usage for your instance metadata requests. + HTTPTokens *string `json:"http_tokens,omitempty" cty:"http_tokens"` +} + type terraformLaunchTemplate struct { // Name is the name of the launch template Name *string `json:"name,omitempty" cty:"name"` @@ -128,6 +135,8 @@ type terraformLaunchTemplate struct { KeyName *terraform.Literal `json:"key_name,omitempty" cty:"key_name"` // MarketOptions are the spot pricing options MarketOptions []*terraformLaunchTemplateMarketOptions `json:"instance_market_options,omitempty" cty:"instance_market_options"` + // MetadataOptions are the instance metadata options. + MetadataOptions *terraformLaunchTemplateInstanceMetadata `json:"metadata_options,omitempty" cty:"metadata_options"` // Monitoring are the instance monitoring options Monitoring []*terraformLaunchTemplateMonitoring `json:"monitoring,omitempty" cty:"monitoring"` // NetworkInterfaces are the networking options @@ -173,6 +182,10 @@ func (t *LaunchTemplate) RenderTerraform(target *terraform.TerraformTarget, a, e ImageID: image, InstanceType: e.InstanceType, Lifecycle: &terraform.Lifecycle{CreateBeforeDestroy: fi.Bool(true)}, + MetadataOptions: &terraformLaunchTemplateInstanceMetadata{ + HTTPTokens: e.HTTPTokens, + HTTPPutResponseHopLimit: e.HTTPPutResponseHopLimit, + }, NetworkInterfaces: []*terraformLaunchTemplateNetworkInterface{ { AssociatePublicIPAddress: e.AssociatePublicIP, diff --git a/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_terraform_test.go b/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_terraform_test.go index 613184278b379..d07cae382f1d2 100644 --- a/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_terraform_test.go +++ b/upup/pkg/fi/cloudup/awstasks/launchtemplate_target_terraform_test.go @@ -48,7 +48,9 @@ func TestLaunchTemplateTerraformRender(t *testing.T) { {Name: fi.String("nodes-1"), ID: fi.String("1111")}, {Name: fi.String("nodes-2"), ID: fi.String("2222")}, }, - Tenancy: fi.String("dedicated"), + Tenancy: fi.String("dedicated"), + HTTPTokens: fi.String("optional"), + HTTPPutResponseHopLimit: fi.Int64(1), }, Expected: `provider "aws" { region = "eu-west-2" @@ -72,6 +74,10 @@ resource "aws_launch_template" "test" { lifecycle { create_before_destroy = true } + metadata_options { + http_put_response_hop_limit = 1 + http_tokens = "optional" + } monitoring { enabled = true } @@ -126,7 +132,9 @@ terraform { {Name: fi.String("nodes-1"), ID: fi.String("1111")}, {Name: fi.String("nodes-2"), ID: fi.String("2222")}, }, - Tenancy: fi.String("dedicated"), + Tenancy: fi.String("dedicated"), + HTTPTokens: fi.String("required"), + HTTPPutResponseHopLimit: fi.Int64(5), }, Expected: `provider "aws" { region = "eu-west-2" @@ -151,6 +159,10 @@ resource "aws_launch_template" "test" { lifecycle { create_before_destroy = true } + metadata_options { + http_put_response_hop_limit = 5 + http_tokens = "required" + } monitoring { enabled = true }