Skip to content
Open
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
2 changes: 2 additions & 0 deletions pkg/asset/installconfig/aws/instancetypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type InstanceType struct {
DefaultVCpus int64
MemInMiB int64
Arches []string
Hypervisor string
Networking Networking
Features []string
}
Expand All @@ -38,6 +39,7 @@ func instanceTypes(ctx context.Context, client *ec2.Client) (map[string]Instance
typeInfo := InstanceType{
DefaultVCpus: int64(aws.ToInt32(sdkTypeInfo.VCpuInfo.DefaultVCpus)),
MemInMiB: aws.ToInt64(sdkTypeInfo.MemoryInfo.SizeInMiB),
Hypervisor: string(sdkTypeInfo.Hypervisor),
}

for _, arch := range sdkTypeInfo.ProcessorInfo.SupportedArchitectures {
Expand Down
27 changes: 20 additions & 7 deletions pkg/asset/installconfig/aws/subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type Subnet struct {
// CIDR is the subnet's CIDR block.
CIDR string

// IPv6CIDR is the subnet's associated IPv6 CIDR block.
IPv6CIDR string

// Public is the flag to define the subnet public.
Public bool

Expand Down Expand Up @@ -118,15 +121,25 @@ func subnets(ctx context.Context, client *ec2.Client, subnetIDs []string, vpcID
return fmt.Errorf("all subnets must belong to the same VPC: %s is from %s, but %s is from %s", *subnet.SubnetId, *subnet.VpcId, vpcFromSubnet, subnetGroups.VpcID)
}

// We currently can only handle subnets with one associated IPv6 CIDR
var ipv6CIDR string
for _, snAssoc := range subnet.Ipv6CidrBlockAssociationSet {
if snAssoc.Ipv6CidrBlock != nil &&
snAssoc.Ipv6CidrBlockState != nil && snAssoc.Ipv6CidrBlockState.State == ec2types.SubnetCidrBlockStateCodeAssociated {
ipv6CIDR = aws.ToString(snAssoc.Ipv6CidrBlock)
}
}

// At this point, we should be safe to dereference these fields.
metas[*subnet.SubnetId] = Subnet{
ID: *subnet.SubnetId,
ARN: *subnet.SubnetArn,
Zone: &Zone{Name: *subnet.AvailabilityZone},
CIDR: ptr.Deref(subnet.CidrBlock, ""),
Public: false,
Tags: FromAWSTags(subnet.Tags),
VPCID: *subnet.VpcId,
ID: *subnet.SubnetId,
ARN: *subnet.SubnetArn,
Zone: &Zone{Name: *subnet.AvailabilityZone},
CIDR: aws.ToString(subnet.CidrBlock),
IPv6CIDR: ipv6CIDR,
Public: false,
Tags: FromAWSTags(subnet.Tags),
VPCID: *subnet.VpcId,
}
zoneNames = append(zoneNames, *subnet.AvailabilityZone)
}
Expand Down
84 changes: 65 additions & 19 deletions pkg/asset/installconfig/aws/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ var computeReq = resourceRequirements{
minimumMemory: 8192,
}

const (
subnetTypePrivate = "private"
subnetTypePublic = "public"
subnetTypeEdge = "edge"
)

// Validate executes platform-specific validation.
func Validate(ctx context.Context, meta *Metadata, config *types.InstallConfig) error {
allErrs := field.ErrorList{}
Expand Down Expand Up @@ -299,7 +305,6 @@ func (sdg *subnetDataGroups) From(ctx context.Context, meta *Metadata, providedS
// validateSubnets ensures BYO subnets are valid.
func validateSubnets(ctx context.Context, meta *Metadata, fldPath *field.Path, config *types.InstallConfig) field.ErrorList {
allErrs := field.ErrorList{}
networking := config.Networking
providedSubnets := config.AWS.VPC.Subnets
publish := config.Publish

Expand Down Expand Up @@ -330,16 +335,16 @@ func validateSubnets(ctx context.Context, meta *Metadata, fldPath *field.Path, c
}

allErrs = append(allErrs, validateSharedSubnets(ctx, meta, fldPath)...)
allErrs = append(allErrs, validateSubnetCIDR(fldPath, subnetDataGroups.Private, networking.MachineNetwork)...)
allErrs = append(allErrs, validateSubnetCIDR(fldPath, subnetDataGroups.Public, networking.MachineNetwork)...)
allErrs = append(allErrs, validateSubnetCIDRs(fldPath, subnetDataGroups.Private, config)...)
allErrs = append(allErrs, validateSubnetCIDRs(fldPath, subnetDataGroups.Public, config)...)

if len(subnetsWithRole) > 0 {
allErrs = append(allErrs, validateSubnetRoles(fldPath, subnetsWithRole, subnetDataGroups, config)...)
} else {
allErrs = append(allErrs, validateUntaggedSubnets(ctx, fldPath, meta, subnetDataGroups)...)
allErrs = append(allErrs, validateDuplicateSubnetZones(fldPath, subnetDataGroups.Private, "private")...)
allErrs = append(allErrs, validateDuplicateSubnetZones(fldPath, subnetDataGroups.Public, "public")...)
allErrs = append(allErrs, validateDuplicateSubnetZones(fldPath, subnetDataGroups.Edge, "edge")...)
allErrs = append(allErrs, validateDuplicateSubnetZones(fldPath, subnetDataGroups.Private, subnetTypePrivate)...)
allErrs = append(allErrs, validateDuplicateSubnetZones(fldPath, subnetDataGroups.Public, subnetTypePublic)...)
allErrs = append(allErrs, validateDuplicateSubnetZones(fldPath, subnetDataGroups.Edge, subnetTypeEdge)...)
}

privateZones := sets.New[string]()
Expand All @@ -366,17 +371,32 @@ func validateMachinePool(ctx context.Context, meta *Metadata, fldPath *field.Pat
// Edge Compute Pool / AWS Local Zones:
// - is valid when installing in existing VPC; or
// - is valid in new VPC when Local Zone name is defined
// - in dualstack networking: is valid when using local zones and installing in existing VPC
if poolName == types.MachinePoolEdgeRoleName {
if len(platform.VPC.Subnets) > 0 {
subnetFp := field.NewPath("platform", "aws", "vpc", "subnets")

edgeSubnets, err := meta.EdgeSubnets(ctx)
if err != nil {
errMsg := fmt.Sprintf("%s pool. %v", poolName, err.Error())
return append(allErrs, field.Invalid(field.NewPath("platform", "aws", "vpc", "subnets"), platform.VPC.Subnets, errMsg))
return append(allErrs, field.Invalid(subnetFp, platform.VPC.Subnets, errMsg))
}
if len(edgeSubnets) == 0 {
return append(allErrs, field.Required(fldPath, "the provided subnets must include valid subnets for the specified edge zones"))
}

if platform.IPFamily.DualStackEnabled() {
for _, sn := range edgeSubnets {
if sn.Zone.Type == awstypes.WavelengthZoneType {
allErrs = append(allErrs, field.Invalid(subnetFp, platform.VPC.Subnets, fmt.Sprintf("ipFamily %s is not supported for subnets in wavelength zones", platform.IPFamily)))
}
}
}
} else {
if platform.IPFamily.DualStackEnabled() {
return append(allErrs, field.Forbidden(fldPath, fmt.Sprintf("ipFamily %s is only supported with user-provided subnets for edge machine pools", string(platform.IPFamily))))
}

if pool.Zones == nil || len(pool.Zones) == 0 {
return append(allErrs, field.Required(fldPath, "zone is required when using edge machine pools"))
}
Expand Down Expand Up @@ -449,12 +469,18 @@ func validateMachinePool(ctx context.Context, meta *Metadata, fldPath *field.Pat
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), pool.InstanceType, errMsg))
}

// dual-stack: the instance type must support IPv6 networking
if platform.IPFamily.DualStackEnabled() {
// The instance type must support IPv6 networking
if !typeMeta.Networking.IPv6Supported {
errMsg := fmt.Sprintf("instance type %s does not support IPv6 networking", pool.InstanceType)
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), pool.InstanceType, errMsg))
}

// The instance type must be Nitro-based to enable IPv6 IMDS endpoint
if typeMeta.Hypervisor != string(ec2types.InstanceTypeHypervisorNitro) {
errMsg := fmt.Sprintf("instance type %s is not Nitro-based", pool.InstanceType)
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), pool.InstanceType, errMsg))
}
}
} else {
errMsg := fmt.Sprintf("instance type %s not found", pool.InstanceType)
Expand Down Expand Up @@ -527,27 +553,47 @@ func validateSecurityGroupIDs(ctx context.Context, meta *Metadata, fldPath *fiel
return allErrs
}

func validateSubnetCIDR(fldPath *field.Path, subnetDataGroup map[string]subnetData, networks []types.MachineNetworkEntry) field.ErrorList {
func validateSubnetCIDRs(fldPath *field.Path, subnetDataGroup map[string]subnetData, ic *types.InstallConfig) field.ErrorList {
allErrs := field.ErrorList{}

for id, subnetData := range subnetDataGroup {
fp := fldPath.Index(subnetData.Idx)
cidr, _, err := net.ParseCIDR(subnetData.CIDR)
if err != nil {
allErrs = append(allErrs, field.Invalid(fp, id, err.Error()))
continue

// Validate subnetIPv4 CIDR
if len(subnetData.CIDR) == 0 {
allErrs = append(allErrs, field.Required(fp, "subnet does not have an associated IPv4 CIDR block"))
} else {
allErrs = append(allErrs, validateMachineNetworksContainSubnetCIDR(fp, ic.MachineNetwork, id, subnetData.CIDR)...)
}

// If dualstack is enabled, the subnet must also have an IPv6 CIDR
if ic.AWS.IPFamily.DualStackEnabled() {
if len(subnetData.IPv6CIDR) == 0 {
allErrs = append(allErrs, field.Required(fp, "subnet does not have an associated IPv6 CIDR block"))
} else {
allErrs = append(allErrs, validateMachineNetworksContainSubnetCIDR(fp, ic.MachineNetwork, id, subnetData.IPv6CIDR)...)
}
}
allErrs = append(allErrs, validateMachineNetworksContainIP(fp, networks, id, cidr)...)
}

return allErrs
}

func validateMachineNetworksContainIP(fldPath *field.Path, networks []types.MachineNetworkEntry, subnetName string, ip net.IP) field.ErrorList {
func validateMachineNetworksContainSubnetCIDR(fldPath *field.Path, networks []types.MachineNetworkEntry, subnetName string, cidr string) field.ErrorList {
allErrs := field.ErrorList{}

cidrIP, _, err := net.ParseCIDR(cidr)
if err != nil {
return append(allErrs, field.Invalid(fldPath, subnetName, err.Error()))
}

for _, network := range networks {
if network.CIDR.Contains(ip) {
if network.CIDR.Contains(cidrIP) {
return nil
}
}
return field.ErrorList{field.Invalid(fldPath, subnetName, fmt.Sprintf("subnet's CIDR range start %s is outside of the specified machine networks", ip))}

return append(allErrs, field.Invalid(fldPath, subnetName, fmt.Sprintf("subnet's CIDR range start %s is outside of the specified machine networks", cidrIP)))
}

func validateDuplicateSubnetZones(fldPath *field.Path, subnetDataGroup map[string]subnetData, typ string) field.ErrorList {
Expand Down Expand Up @@ -659,9 +705,9 @@ func validateSubnetRoles(fldPath *field.Path, subnetsWithRole map[awstypes.Subne
}

if ingressSubnet.Public != config.PublicIngress() {
subnetType := "private"
subnetType := subnetTypePrivate
if ingressSubnet.Public {
subnetType = "public"
subnetType = subnetTypePublic
}
allErrs = append(allErrs, field.Invalid(fldPath.Index(ingressSubnet.Idx), ingressSubnet.ID,
fmt.Sprintf("subnet %s has role %s and is %s, which is not allowed when publish is set to %s", ingressSubnet.ID, awstypes.IngressControllerLBSubnetRole, subnetType, config.Publish)))
Expand Down
Loading