Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions pkg/actuators/machine/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,8 @@ func launchInstance(machine *machinev1beta1.Machine, machineProviderConfig *mach
NetworkInterfaces: networkInterfaces,
UserData: &userDataEnc,
Placement: placement,
MetadataOptions: getInstanceMetadataOptionsRequest(machineProviderConfig),
MetadataOptions: getInstanceMetadataOptionsRequest(machineProviderConfig, infra),
PrivateDnsNameOptions: getPrivateDNSNameOptionsRequest(infra),
InstanceMarketOptions: instanceMarketOptions,
CapacityReservationSpecification: capacityReservationSpecification,
CpuOptions: getCPUOptionsRequest(machineProviderConfig),
Expand Down Expand Up @@ -784,7 +785,17 @@ func constructInstancePlacement(machine *machinev1beta1.Machine, machineProvider
return placement, nil
}

func getInstanceMetadataOptionsRequest(providerConfig *machinev1beta1.AWSMachineProviderConfig) *ec2.InstanceMetadataOptionsRequest {
// isAWSDualStack checks if the infrastructure is configured for dual-stack networking.
// It returns true if infra ipFamily is configured with either DualStackIPv6Primary or DualStackIPv4Primary.
func isAWSDualStack(infra *configv1.Infrastructure) bool {
if infra == nil || infra.Status.PlatformStatus == nil || infra.Status.PlatformStatus.AWS == nil {
return false
}
return infra.Status.PlatformStatus.AWS.IPFamily == configv1.DualStackIPv6Primary ||
infra.Status.PlatformStatus.AWS.IPFamily == configv1.DualStackIPv4Primary
}

func getInstanceMetadataOptionsRequest(providerConfig *machinev1beta1.AWSMachineProviderConfig, infra *configv1.Infrastructure) *ec2.InstanceMetadataOptionsRequest {
imdsOptions := &ec2.InstanceMetadataOptionsRequest{}

switch providerConfig.MetadataServiceOptions.Authentication {
Expand All @@ -797,13 +808,35 @@ func getInstanceMetadataOptionsRequest(providerConfig *machinev1beta1.AWSMachine
imdsOptions.HttpTokens = aws.String(ec2.HttpTokensStateRequired)
}

if isAWSDualStack(infra) {
imdsOptions.HttpProtocolIpv6 = ptr.To("enabled")
}

if *imdsOptions == (ec2.InstanceMetadataOptionsRequest{}) {
// return nil instead of empty struct if there is no options set
return nil
}
return imdsOptions
}

func getPrivateDNSNameOptionsRequest(infra *configv1.Infrastructure) *ec2.PrivateDnsNameOptionsRequest {
privateDNSNameOptions := &ec2.PrivateDnsNameOptionsRequest{}

if isAWSDualStack(infra) {
privateDNSNameOptions.EnableResourceNameDnsARecord = ptr.To(true)
privateDNSNameOptions.EnableResourceNameDnsAAAARecord = ptr.To(true)
// Only resource-name supports A and AAAA records for private host names
// See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/hostname-types.html#ec2-instance-private-hostnames
privateDNSNameOptions.HostnameType = ptr.To(string(ec2.HostnameTypeResourceName))
}

if *privateDNSNameOptions == (ec2.PrivateDnsNameOptionsRequest{}) {
// return nil instead of empty struct if there is no options set
return nil
}
return privateDNSNameOptions
}

func getCapacityReservationSpecification(capacityReservationID string) (*ec2.CapacityReservationSpecification, error) {
if capacityReservationID == "" {
// Not targeting any specific Capacity Reservation
Expand Down
239 changes: 238 additions & 1 deletion pkg/actuators/machine/instances_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,110 @@ func TestLaunchInstance(t *testing.T) {
UserData: aws.String(""),
},
},
{
name: "DualStackIPv6Primary with private DNS name and IMDS options",
providerConfig: providerConfig,
subnetOutput: stubDescribeSubnetsOutputProvided(aws.StringValue(providerConfig.Subnet.ID)),
zonesOutput: stubDescribeAvailabilityZonesOutputDefault(),
instancesOutput: stubReservation(stubAMIID, stubInstanceID, "192.168.0.10"),
succeeds: true,
infra: &configv1.Infrastructure{
Status: configv1.InfrastructureStatus{
PlatformStatus: &configv1.PlatformStatus{
AWS: &configv1.AWSPlatformStatus{
IPFamily: configv1.DualStackIPv6Primary,
},
},
},
},
runInstancesInput: &ec2.RunInstancesInput{
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{
Name: aws.String(*providerConfig.IAMInstanceProfile.ID),
},
ImageId: aws.String(*providerConfig.AMI.ID),
InstanceType: &providerConfig.InstanceType,
MinCount: aws.Int64(1),
MaxCount: aws.Int64(1),
KeyName: providerConfig.KeyName,
TagSpecifications: []*ec2.TagSpecification{{
ResourceType: aws.String("instance"),
Tags: stubTagList,
}, {
ResourceType: aws.String("volume"),
Tags: stubTagList,
}},
NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{
{
DeviceIndex: aws.Int64(providerConfig.DeviceIndex),
AssociatePublicIpAddress: providerConfig.PublicIP,
SubnetId: providerConfig.Subnet.ID,
Groups: stubSecurityGroupsDefault,
PrimaryIpv6: aws.Bool(true),
Ipv6AddressCount: aws.Int64(1),
},
},
UserData: aws.String(""),
MetadataOptions: &ec2.InstanceMetadataOptionsRequest{
HttpProtocolIpv6: aws.String("enabled"),
},
PrivateDnsNameOptions: &ec2.PrivateDnsNameOptionsRequest{
EnableResourceNameDnsARecord: aws.Bool(true),
EnableResourceNameDnsAAAARecord: aws.Bool(true),
HostnameType: aws.String("resource-name"),
},
},
},
{
name: "DualStackIPv4Primary with private DNS name and IMDS options",
providerConfig: providerConfig,
subnetOutput: stubDescribeSubnetsOutputProvided(aws.StringValue(providerConfig.Subnet.ID)),
zonesOutput: stubDescribeAvailabilityZonesOutputDefault(),
instancesOutput: stubReservation(stubAMIID, stubInstanceID, "192.168.0.10"),
succeeds: true,
infra: &configv1.Infrastructure{
Status: configv1.InfrastructureStatus{
PlatformStatus: &configv1.PlatformStatus{
AWS: &configv1.AWSPlatformStatus{
IPFamily: configv1.DualStackIPv4Primary,
},
},
},
},
runInstancesInput: &ec2.RunInstancesInput{
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{
Name: aws.String(*providerConfig.IAMInstanceProfile.ID),
},
ImageId: aws.String(*providerConfig.AMI.ID),
InstanceType: &providerConfig.InstanceType,
MinCount: aws.Int64(1),
MaxCount: aws.Int64(1),
KeyName: providerConfig.KeyName,
TagSpecifications: []*ec2.TagSpecification{{
ResourceType: aws.String("instance"),
Tags: stubTagList,
}, {
ResourceType: aws.String("volume"),
Tags: stubTagList,
}},
NetworkInterfaces: []*ec2.InstanceNetworkInterfaceSpecification{
{
DeviceIndex: aws.Int64(providerConfig.DeviceIndex),
AssociatePublicIpAddress: providerConfig.PublicIP,
SubnetId: providerConfig.Subnet.ID,
Groups: stubSecurityGroupsDefault,
},
},
UserData: aws.String(""),
MetadataOptions: &ec2.InstanceMetadataOptionsRequest{
HttpProtocolIpv6: aws.String("enabled"),
},
PrivateDnsNameOptions: &ec2.PrivateDnsNameOptionsRequest{
EnableResourceNameDnsARecord: aws.Bool(true),
EnableResourceNameDnsAAAARecord: aws.Bool(true),
HostnameType: aws.String("resource-name"),
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
Expand Down Expand Up @@ -1377,11 +1481,13 @@ func TestGetInstanceMetadataOptionsRequest(t *testing.T) {
testCases := []struct {
name string
providerConfig *machinev1beta1.AWSMachineProviderConfig
infra *configv1.Infrastructure
expected *ec2.InstanceMetadataOptionsRequest
}{
{
name: "no imds options specified",
providerConfig: &machinev1beta1.AWSMachineProviderConfig{},
infra: nil,
expected: nil,
},
{
Expand All @@ -1391,6 +1497,7 @@ func TestGetInstanceMetadataOptionsRequest(t *testing.T) {
Authentication: machinev1beta1.MetadataServiceAuthenticationRequired,
},
},
infra: nil,
expected: &ec2.InstanceMetadataOptionsRequest{
HttpTokens: aws.String(ec2.HttpTokensStateRequired),
},
Expand All @@ -1402,6 +1509,7 @@ func TestGetInstanceMetadataOptionsRequest(t *testing.T) {
Authentication: machinev1beta1.MetadataServiceAuthenticationOptional,
},
},
infra: nil,
expected: &ec2.InstanceMetadataOptionsRequest{
HttpTokens: aws.String(ec2.HttpTokensStateOptional),
},
Expand All @@ -1414,13 +1522,142 @@ func TestGetInstanceMetadataOptionsRequest(t *testing.T) {
Authentication: "foooobaaaar",
},
},
infra: nil,
expected: nil,
},
{
name: "DualStackIPv6Primary IPFamily",
providerConfig: &machinev1beta1.AWSMachineProviderConfig{},
infra: &configv1.Infrastructure{
Status: configv1.InfrastructureStatus{
PlatformStatus: &configv1.PlatformStatus{
AWS: &configv1.AWSPlatformStatus{
IPFamily: configv1.DualStackIPv6Primary,
},
},
},
},
expected: &ec2.InstanceMetadataOptionsRequest{
HttpProtocolIpv6: aws.String("enabled"),
},
},
{
name: "DualStackIPv4Primary IPFamily",
providerConfig: &machinev1beta1.AWSMachineProviderConfig{},
infra: &configv1.Infrastructure{
Status: configv1.InfrastructureStatus{
PlatformStatus: &configv1.PlatformStatus{
AWS: &configv1.AWSPlatformStatus{
IPFamily: configv1.DualStackIPv4Primary,
},
},
},
},
expected: &ec2.InstanceMetadataOptionsRequest{
HttpProtocolIpv6: aws.String("enabled"),
},
},
{
name: "IPv4 IPFamily",
providerConfig: &machinev1beta1.AWSMachineProviderConfig{},
infra: &configv1.Infrastructure{
Status: configv1.InfrastructureStatus{
PlatformStatus: &configv1.PlatformStatus{
AWS: &configv1.AWSPlatformStatus{
IPFamily: configv1.IPv4,
},
},
},
},
expected: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
g := gmg.NewWithT(t)
req := getInstanceMetadataOptionsRequest(tc.providerConfig, tc.infra)
g.Expect(req).To(gmg.BeEquivalentTo(tc.expected))
})
}
}

func TestGetPrivateDNSNameOptionsRequest(t *testing.T) {
testCases := []struct {
name string
infra *configv1.Infrastructure
expected *ec2.PrivateDnsNameOptionsRequest
}{
{
name: "no infra specified",
infra: nil,
expected: nil,
},
{
name: "empty infra",
infra: &configv1.Infrastructure{},
expected: nil,
},
{
name: "infra with no AWS platform status",
infra: &configv1.Infrastructure{
Status: configv1.InfrastructureStatus{
PlatformStatus: &configv1.PlatformStatus{},
},
},
expected: nil,
},
{
name: "DualStackIPv6Primary IPFamily",
infra: &configv1.Infrastructure{
Status: configv1.InfrastructureStatus{
PlatformStatus: &configv1.PlatformStatus{
AWS: &configv1.AWSPlatformStatus{
IPFamily: configv1.DualStackIPv6Primary,
},
},
},
},
expected: &ec2.PrivateDnsNameOptionsRequest{
EnableResourceNameDnsARecord: aws.Bool(true),
EnableResourceNameDnsAAAARecord: aws.Bool(true),
HostnameType: aws.String(ec2.HostnameTypeResourceName),
},
},
{
name: "DualStackIPv4Primary IPFamily",
infra: &configv1.Infrastructure{
Status: configv1.InfrastructureStatus{
PlatformStatus: &configv1.PlatformStatus{
AWS: &configv1.AWSPlatformStatus{
IPFamily: configv1.DualStackIPv4Primary,
},
},
},
},
expected: &ec2.PrivateDnsNameOptionsRequest{
EnableResourceNameDnsARecord: aws.Bool(true),
EnableResourceNameDnsAAAARecord: aws.Bool(true),
HostnameType: aws.String(ec2.HostnameTypeResourceName),
},
},
{
name: "IPv4 IPFamily",
infra: &configv1.Infrastructure{
Status: configv1.InfrastructureStatus{
PlatformStatus: &configv1.PlatformStatus{
AWS: &configv1.AWSPlatformStatus{
IPFamily: configv1.IPv4,
},
},
},
},
expected: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
g := gmg.NewWithT(t)
req := getInstanceMetadataOptionsRequest(tc.providerConfig)
req := getPrivateDNSNameOptionsRequest(tc.infra)
g.Expect(req).To(gmg.BeEquivalentTo(tc.expected))
})
}
Expand Down