Skip to content

Commit 64b1546

Browse files
[release-2.4] 🐛 ec2: instances: fix assigning public IP (#4908)
* 🐛ec2: instances: fix check for public subnets It's not enough to check MapPublicIPOnLaunch since public subnets can have that off. * 🐛ec2: instances: fix assigning public IP In the scenario where the user brings their own VPC, if no subnet ID is set in the machine spec and PublicIP is true, CAPA will choose one from the available public subnets. However, if the subnet doesn't have MapPublicIPOnLaunch == true, the instance will not be assigned a public IP. As a result, the instance will have no internet access, contrary to the user's expectation. This change guarantees an instance will be assigned a public IP even if the subnet doesn't do it on instance launch. Instead, we set the option in the instance's network interface. * 🌱ec2: instances: add unit tests for MapPublicIpOnLaunch=false The tests check that a NetworkInterface is defined with `AssociatePublicIpAddress` in the `RunInstances` input. --------- Co-authored-by: Rafael Fonseca <[email protected]>
1 parent 557a58d commit 64b1546

File tree

8 files changed

+555
-5
lines changed

8 files changed

+555
-5
lines changed

api/v1beta1/awscluster_conversion.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
5757
dst.Status.Bastion.InstanceMetadataOptions = restored.Status.Bastion.InstanceMetadataOptions
5858
dst.Status.Bastion.PlacementGroupName = restored.Status.Bastion.PlacementGroupName
5959
dst.Status.Bastion.PrivateDNSName = restored.Status.Bastion.PrivateDNSName
60+
dst.Status.Bastion.PublicIPOnLaunch = restored.Status.Bastion.PublicIPOnLaunch
6061
}
6162
dst.Spec.Partition = restored.Spec.Partition
6263

api/v1beta1/zz_generated.conversion.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta2/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ type Instance struct {
237237
// PrivateDNSName is the options for the instance hostname.
238238
// +optional
239239
PrivateDNSName *PrivateDNSName `json:"privateDnsName,omitempty"`
240+
241+
// PublicIPOnLaunch is the option to associate a public IP on instance launch
242+
// +optional
243+
PublicIPOnLaunch *bool `json:"publicIPOnLaunch,omitempty"`
240244
}
241245

242246
// InstanceMetadataState describes the state of InstanceMetadataOptions.HttpEndpoint and InstanceMetadataOptions.InstanceMetadataTags

api/v1beta2/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,10 @@ spec:
11321132
privateIp:
11331133
description: The private IPv4 address assigned to the instance.
11341134
type: string
1135+
publicIPOnLaunch:
1136+
description: PublicIPOnLaunch is the option to associate a public
1137+
IP on instance launch
1138+
type: boolean
11351139
publicIp:
11361140
description: The public IPv4 address assigned to the instance,
11371141
if applicable.
@@ -2984,6 +2988,10 @@ spec:
29842988
privateIp:
29852989
description: The private IPv4 address assigned to the instance.
29862990
type: string
2991+
publicIPOnLaunch:
2992+
description: PublicIPOnLaunch is the option to associate a public
2993+
IP on instance launch
2994+
type: boolean
29872995
publicIp:
29882996
description: The public IPv4 address assigned to the instance,
29892997
if applicable.

config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1907,6 +1907,10 @@ spec:
19071907
privateIp:
19081908
description: The private IPv4 address assigned to the instance.
19091909
type: string
1910+
publicIPOnLaunch:
1911+
description: PublicIPOnLaunch is the option to associate a public
1912+
IP on instance launch
1913+
type: boolean
19101914
publicIp:
19111915
description: The public IPv4 address assigned to the instance,
19121916
if applicable.

pkg/cloud/services/ec2/instances.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,21 @@ func (s *Service) CreateInstance(scope *scope.MachineScope, userData []byte, use
181181
}
182182
input.SubnetID = subnetID
183183

184+
if ptr.Deref(scope.AWSMachine.Spec.PublicIP, false) {
185+
subnets, err := s.getFilteredSubnets(&ec2.Filter{
186+
Name: aws.String("subnet-id"),
187+
Values: aws.StringSlice([]string{subnetID}),
188+
})
189+
if err != nil {
190+
return nil, fmt.Errorf("could not query if subnet has MapPublicIpOnLaunch set: %w", err)
191+
}
192+
if len(subnets) == 0 {
193+
return nil, fmt.Errorf("expected to find subnet %q", subnetID)
194+
}
195+
// If the subnet does not assign public IPs, set that option in the instance's network interface
196+
input.PublicIPOnLaunch = ptr.To(!aws.BoolValue(subnets[0].MapPublicIpOnLaunch))
197+
}
198+
184199
if !scope.IsControlPlaneExternallyManaged() && !scope.IsExternallyManaged() && !scope.IsEKSManaged() && s.scope.Network().APIServerELB.DNSName == "" {
185200
record.Eventf(s.scope.InfraCluster(), "FailedCreateInstance", "Failed to run controlplane, APIServer ELB not available")
186201
return nil, awserrors.NewFailedDependency("failed to run controlplane, APIServer ELB not available")
@@ -334,7 +349,7 @@ func (s *Service) findSubnet(scope *scope.MachineScope) (string, error) {
334349
*subnet.SubnetId, *subnet.AvailabilityZone, *failureDomain)
335350
continue
336351
}
337-
if scope.AWSMachine.Spec.PublicIP != nil && *scope.AWSMachine.Spec.PublicIP && !*subnet.MapPublicIpOnLaunch {
352+
if scope.AWSMachine.Spec.PublicIP != nil && *scope.AWSMachine.Spec.PublicIP && !s.scope.Subnets().FindByID(*subnet.SubnetId).IsPublic {
338353
errMessage += fmt.Sprintf(" subnet %q is a private subnet.", *subnet.SubnetId)
339354
continue
340355
}
@@ -538,13 +553,25 @@ func (s *Service) runInstance(role string, i *infrav1.Instance) (*infrav1.Instan
538553
DeviceIndex: aws.Int64(int64(index)),
539554
})
540555
}
556+
netInterfaces[0].AssociatePublicIpAddress = i.PublicIPOnLaunch
541557

542558
input.NetworkInterfaces = netInterfaces
543559
} else {
544-
input.SubnetId = aws.String(i.SubnetID)
560+
if ptr.Deref(i.PublicIPOnLaunch, false) {
561+
input.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
562+
{
563+
DeviceIndex: aws.Int64(0),
564+
SubnetId: aws.String(i.SubnetID),
565+
Groups: aws.StringSlice(i.SecurityGroupIDs),
566+
AssociatePublicIpAddress: i.PublicIPOnLaunch,
567+
},
568+
}
569+
} else {
570+
input.SubnetId = aws.String(i.SubnetID)
545571

546-
if len(i.SecurityGroupIDs) > 0 {
547-
input.SecurityGroupIds = aws.StringSlice(i.SecurityGroupIDs)
572+
if len(i.SecurityGroupIDs) > 0 {
573+
input.SecurityGroupIds = aws.StringSlice(i.SecurityGroupIDs)
574+
}
548575
}
549576
}
550577

0 commit comments

Comments
 (0)