Skip to content

Commit 61810a6

Browse files
committed
aws/publicIpv4Pool: validate if Public IPv4 pool exists
Validate if Public IPv4 Pool (platform.aws.publicIpv4PoolId) exists, and has enough free IPs in the pool when installing a cluster with publish strategy external.
1 parent 50c0a9d commit 61810a6

File tree

3 files changed

+75
-0
lines changed

3 files changed

+75
-0
lines changed

pkg/asset/installconfig/aws/ec2.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package aws
22

33
import (
44
"context"
5+
"fmt"
56
"time"
67

78
"github.com/aws/aws-sdk-go/aws"
@@ -28,3 +29,24 @@ func DescribeSecurityGroups(ctx context.Context, session *session.Session, secur
2829
}
2930
return sgOutput.SecurityGroups, nil
3031
}
32+
33+
// DescribePublicIpv4Pool returns the ec2 public IPv4 Pool attributes from the given ID.
34+
func DescribePublicIpv4Pool(ctx context.Context, session *session.Session, region string, poolID string) (*ec2.PublicIpv4Pool, error) {
35+
client := ec2.New(session, aws.NewConfig().WithRegion(region))
36+
37+
cctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
38+
defer cancel()
39+
40+
poolOutputs, err := client.DescribePublicIpv4PoolsWithContext(cctx, &ec2.DescribePublicIpv4PoolsInput{PoolIds: []*string{aws.String(poolID)}})
41+
if err != nil {
42+
return nil, err
43+
}
44+
if len(poolOutputs.PublicIpv4Pools) == 0 {
45+
return nil, fmt.Errorf("public IPv4 Pool not found: %s", poolID)
46+
}
47+
// it should not happen
48+
if len(poolOutputs.PublicIpv4Pools) > 1 {
49+
return nil, fmt.Errorf("more than one Public IPv4 Pool: %s", poolID)
50+
}
51+
return poolOutputs.PublicIpv4Pools[0], nil
52+
}

pkg/asset/installconfig/aws/validation.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func Validate(ctx context.Context, meta *Metadata, config *types.InstallConfig)
4646
return errors.New(field.Required(field.NewPath("platform", "aws"), "AWS validation requires an AWS platform configuration").Error())
4747
}
4848
allErrs = append(allErrs, validateAMI(ctx, config)...)
49+
allErrs = append(allErrs, validatePublicIpv4Pool(ctx, meta, field.NewPath("platform", "aws", "publicIpv4PoolId"), config)...)
4950
allErrs = append(allErrs, validatePlatform(ctx, meta, field.NewPath("platform", "aws"), config.Platform.AWS, config.Networking, config.Publish)...)
5051

5152
if config.ControlPlane != nil {
@@ -141,6 +142,47 @@ func validateAMI(ctx context.Context, config *types.InstallConfig) field.ErrorLi
141142
return field.ErrorList{field.Required(field.NewPath("platform", "aws", "amiID"), "AMI must be provided")}
142143
}
143144

145+
func validatePublicIpv4Pool(ctx context.Context, meta *Metadata, fldPath *field.Path, config *types.InstallConfig) field.ErrorList {
146+
allErrs := field.ErrorList{}
147+
148+
if config.Platform.AWS.PublicIpv4Pool == "" {
149+
return nil
150+
}
151+
poolID := config.Platform.AWS.PublicIpv4Pool
152+
if config.Publish != types.ExternalPublishingStrategy {
153+
return append(allErrs, field.Invalid(fldPath, poolID, fmt.Errorf("publish strategy %s can't be used with custom Public IPv4 Pools", config.Publish).Error()))
154+
}
155+
156+
// Pool validations
157+
// Resources claiming Public IPv4 from Pool in regular 'External' installations:
158+
// 1* for Bootsrtap
159+
// N*Zones for NAT Gateways
160+
// N*Zones for API LB
161+
// N*Zones for Ingress LB
162+
allzones, err := meta.AvailabilityZones(ctx)
163+
if err != nil {
164+
return append(allErrs, field.InternalError(fldPath, err))
165+
}
166+
totalPublicIPRequired := int64(1 + (len(allzones) * 3))
167+
168+
sess, err := meta.Session(ctx)
169+
if err != nil {
170+
return append(allErrs, field.Invalid(fldPath, nil, fmt.Sprintf("unable to start a session: %s", err.Error())))
171+
}
172+
publicIpv4Pool, err := DescribePublicIpv4Pool(ctx, sess, config.Platform.AWS.Region, poolID)
173+
if err != nil {
174+
return append(allErrs, field.Invalid(fldPath, poolID, err.Error()))
175+
}
176+
177+
got := aws.Int64Value(publicIpv4Pool.TotalAvailableAddressCount)
178+
if got < totalPublicIPRequired {
179+
err = fmt.Errorf("required a minimum of %d Public IPv4 IPs available in the pool %s, got %d", totalPublicIPRequired, poolID, got)
180+
return append(allErrs, field.InternalError(fldPath, err))
181+
}
182+
183+
return nil
184+
}
185+
144186
func validateSubnets(ctx context.Context, meta *Metadata, fldPath *field.Path, subnets []string, networking *types.Networking, publish types.PublishingStrategy) field.ErrorList {
145187
allErrs := field.ErrorList{}
146188
privateSubnets, err := meta.PrivateSubnets(ctx)

pkg/asset/installconfig/aws/validation_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,17 @@ func TestValidate(t *testing.T) {
807807
publicSubnets: validPublicSubnets(),
808808
proxy: "http://proxy.com",
809809
expectErr: `^\Qplatform.aws.serviceEndpoints[0].url: Invalid value: "http://test": Head "http://test": dial tcp: lookup test\E.*: no such host$`,
810+
}, {
811+
name: "invalid public ipv4 pool private installation",
812+
installConfig: func() *types.InstallConfig {
813+
c := validInstallConfig()
814+
c.Publish = types.InternalPublishingStrategy
815+
c.Platform.AWS.PublicIpv4Pool = "ipv4pool-ec2-123"
816+
c.Platform.AWS.Subnets = []string{}
817+
return c
818+
}(),
819+
availZones: validAvailZones(),
820+
expectErr: `^platform.aws.publicIpv4PoolId: Invalid value: "ipv4pool-ec2-123": publish strategy Internal can't be used with custom Public IPv4 Pools$`,
810821
}}
811822

812823
for _, test := range tests {

0 commit comments

Comments
 (0)