diff --git a/examples/38-cluster-subnets-sgs.yaml b/examples/38-cluster-subnets-sgs.yaml new file mode 100644 index 0000000000..67e1b8202a --- /dev/null +++ b/examples/38-cluster-subnets-sgs.yaml @@ -0,0 +1,22 @@ +# An example config for updating API server endpoint access, public access CIDRs, and control plane subnets and security groups. +# To perform the update, run `eksctl utils update-cluster-vpc-config -f 38-cluster-subnets-sgs.yaml` + +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig +metadata: + name: cluster-38 + region: us-west-2 + +iam: + withOIDC: true + +vpc: + controlPlaneSubnetIDs: [subnet-1234, subnet-5678] + controlPlaneSecurityGroupIDs: [sg-1234, sg-5678] + clusterEndpoints: + publicAccess: true + privateAccess: true + publicAccessCIDRs: ["1.1.1.1/32"] + +managedNodeGroups: + - name: mng1 diff --git a/integration/tests/managed/managed_nodegroup_test.go b/integration/tests/managed/managed_nodegroup_test.go index 32a4940889..f5fee494c4 100644 --- a/integration/tests/managed/managed_nodegroup_test.go +++ b/integration/tests/managed/managed_nodegroup_test.go @@ -6,10 +6,13 @@ package managed import ( "context" "fmt" + "strings" "testing" "time" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ec2" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" awseks "github.com/aws/aws-sdk-go-v2/service/eks" harness "github.com/dlespiau/kube-test-harness" @@ -27,6 +30,7 @@ import ( clusterutils "github.com/weaveworks/eksctl/integration/utilities/cluster" "github.com/weaveworks/eksctl/integration/utilities/kube" api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + "github.com/weaveworks/eksctl/pkg/awsapi" "github.com/weaveworks/eksctl/pkg/eks" "github.com/weaveworks/eksctl/pkg/testutils" ) @@ -521,6 +525,107 @@ var _ = Describe("(Integration) Create Managed Nodegroups", func() { Expect(cmd).To(RunSuccessfully()) }) }) + + Context("eksctl utils update-cluster-vpc-config", Serial, func() { + makeAWSProvider := func(ctx context.Context, clusterConfig *api.ClusterConfig) api.ClusterProvider { + clusterProvider, err := eks.New(ctx, &api.ProviderConfig{Region: params.Region}, clusterConfig) + Expect(err).NotTo(HaveOccurred()) + return clusterProvider.AWSProvider + } + getPrivateSubnetIDs := func(ctx context.Context, ec2API awsapi.EC2, vpcID string) []string { + out, err := ec2API.DescribeSubnets(ctx, &ec2.DescribeSubnetsInput{ + Filters: []ec2types.Filter{ + { + Name: aws.String("vpc-id"), + Values: []string{vpcID}, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + var subnetIDs []string + for _, s := range out.Subnets { + if !*s.MapPublicIpOnLaunch { + subnetIDs = append(subnetIDs, *s.SubnetId) + } + } + return subnetIDs + } + It("should update the VPC config", func() { + clusterConfig := makeClusterConfig() + ctx := context.Background() + awsProvider := makeAWSProvider(ctx, clusterConfig) + cluster, err := awsProvider.EKS().DescribeCluster(ctx, &awseks.DescribeClusterInput{ + Name: aws.String(params.ClusterName), + }) + Expect(err).NotTo(HaveOccurred(), "error describing cluster") + clusterSubnetIDs := getPrivateSubnetIDs(ctx, awsProvider.EC2(), *cluster.Cluster.ResourcesVpcConfig.VpcId) + Expect(len(cluster.Cluster.ResourcesVpcConfig.SecurityGroupIds) > 0).To(BeTrue(), "at least one security group ID must be associated with the cluster") + + clusterVPC := &api.ClusterVPC{ + ClusterEndpoints: &api.ClusterEndpoints{ + PrivateAccess: api.Enabled(), + PublicAccess: api.Enabled(), + }, + PublicAccessCIDRs: []string{"127.0.0.1/32"}, + ControlPlaneSubnetIDs: clusterSubnetIDs, + ControlPlaneSecurityGroupIDs: []string{cluster.Cluster.ResourcesVpcConfig.SecurityGroupIds[0]}, + } + By("accepting CLI options") + cmd := params.EksctlUtilsCmd.WithArgs( + "update-cluster-vpc-config", + "--cluster", params.ClusterName, + "--private-access", + "--public-access", + "--public-access-cidrs", strings.Join(clusterVPC.PublicAccessCIDRs, ","), + "--control-plane-subnet-ids", strings.Join(clusterVPC.ControlPlaneSubnetIDs, ","), + "--control-plane-security-group-ids", strings.Join(clusterVPC.ControlPlaneSecurityGroupIDs, ","), + "-v4", + "--approve", + ). + WithTimeout(45 * time.Minute) + session := cmd.Run() + Expect(session.ExitCode()).To(Equal(0)) + + formatWithClusterAndRegion := func(format string, values ...any) string { + return fmt.Sprintf(format, append([]any{params.ClusterName, params.Region}, values...)...) + } + Expect(strings.Split(string(session.Buffer().Contents()), "\n")).To(ContainElements( + ContainSubstring(formatWithClusterAndRegion("control plane subnets and security groups for cluster %q in %q have been updated to: "+ + "controlPlaneSubnetIDs=%v, controlPlaneSecurityGroupIDs=%v", clusterVPC.ControlPlaneSubnetIDs, clusterVPC.ControlPlaneSecurityGroupIDs)), + ContainSubstring(formatWithClusterAndRegion("Kubernetes API endpoint access for cluster %q in %q has been updated to: privateAccess=%v, publicAccess=%v", + *clusterVPC.ClusterEndpoints.PrivateAccess, *clusterVPC.ClusterEndpoints.PublicAccess)), + ContainSubstring(formatWithClusterAndRegion("public access CIDRs for cluster %q in %q have been updated to: %v", clusterVPC.PublicAccessCIDRs)), + )) + + By("accepting a config file") + clusterConfig.VPC = clusterVPC + cmd = params.EksctlUtilsCmd.WithArgs( + "update-cluster-vpc-config", + "--config-file", "-", + "-v4", + "--approve", + ). + WithoutArg("--region", params.Region). + WithStdin(clusterutils.Reader(clusterConfig)) + session = cmd.Run() + Expect(session.ExitCode()).To(Equal(0)) + Expect(strings.Split(string(session.Buffer().Contents()), "\n")).To(ContainElements( + ContainSubstring(formatWithClusterAndRegion("Kubernetes API endpoint access for cluster %q in %q is already up-to-date")), + ContainSubstring(formatWithClusterAndRegion("control plane subnet IDs for cluster %q in %q are already up-to-date")), + ContainSubstring(formatWithClusterAndRegion("control plane security group IDs for cluster %q in %q are already up-to-date")), + )) + + By("resetting public access CIDRs") + cmd = params.EksctlUtilsCmd.WithArgs( + "update-cluster-vpc-config", + "--cluster", params.ClusterName, + "--public-access-cidrs", "0.0.0.0/0", + "-v4", + "--approve", + ) + Expect(cmd).To(RunSuccessfully()) + }) + }) }) var _ = SynchronizedAfterSuite(func() {}, func() { diff --git a/pkg/apis/eksctl.io/v1alpha5/assets/schema.json b/pkg/apis/eksctl.io/v1alpha5/assets/schema.json index 122c0a0fde..c21d94e023 100755 --- a/pkg/apis/eksctl.io/v1alpha5/assets/schema.json +++ b/pkg/apis/eksctl.io/v1alpha5/assets/schema.json @@ -655,6 +655,22 @@ "description": "See [managing access to API](/usage/vpc-networking/#managing-access-to-the-kubernetes-api-server-endpoints)", "x-intellij-html-description": "See managing access to API" }, + "controlPlaneSecurityGroupIDs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "configures the security groups for the control plane.", + "x-intellij-html-description": "configures the security groups for the control plane." + }, + "controlPlaneSubnetIDs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "configures the subnets for the control plane.", + "x-intellij-html-description": "configures the subnets for the control plane." + }, "extraCIDRs": { "items": { "type": "string" @@ -733,7 +749,9 @@ "autoAllocateIPv6", "nat", "clusterEndpoints", - "publicAccessCIDRs" + "publicAccessCIDRs", + "controlPlaneSubnetIDs", + "controlPlaneSecurityGroupIDs" ], "additionalProperties": false, "description": "holds global subnet and all child subnets", diff --git a/pkg/apis/eksctl.io/v1alpha5/validation.go b/pkg/apis/eksctl.io/v1alpha5/validation.go index fd5d1c5e2f..bd42ffcb32 100644 --- a/pkg/apis/eksctl.io/v1alpha5/validation.go +++ b/pkg/apis/eksctl.io/v1alpha5/validation.go @@ -318,6 +318,10 @@ func (c *ClusterConfig) ValidateVPCConfig() error { c.VPC.ExtraIPv6CIDRs = cidrs } + if c.VPC.SecurityGroup != "" && len(c.VPC.ControlPlaneSecurityGroupIDs) > 0 { + return errors.New("only one of vpc.securityGroup and vpc.controlPlaneSecurityGroupIDs can be specified") + } + if (c.VPC.IPv6Cidr != "" || c.VPC.IPv6Pool != "") && !c.IPv6Enabled() { return fmt.Errorf("Ipv6Cidr and Ipv6CidrPool are only supported when IPFamily is set to IPv6") } diff --git a/pkg/apis/eksctl.io/v1alpha5/validation_test.go b/pkg/apis/eksctl.io/v1alpha5/validation_test.go index ce3abd67d8..d835aeb5a4 100644 --- a/pkg/apis/eksctl.io/v1alpha5/validation_test.go +++ b/pkg/apis/eksctl.io/v1alpha5/validation_test.go @@ -1332,6 +1332,41 @@ var _ = Describe("ClusterConfig validation", func() { }) }) + + type vpcSecurityGroupEntry struct { + updateVPC func(*api.ClusterVPC) + expectedErr string + } + DescribeTable("vpc.securityGroup and vpc.controlPlaneSecurityGroupIDs", func(e vpcSecurityGroupEntry) { + e.updateVPC(cfg.VPC) + err := cfg.ValidateVPCConfig() + if e.expectedErr != "" { + Expect(err).To(MatchError(ContainSubstring(e.expectedErr))) + } else { + Expect(err).NotTo(HaveOccurred()) + } + }, + Entry("both set", vpcSecurityGroupEntry{ + updateVPC: func(v *api.ClusterVPC) { + v.SecurityGroup = "sg-1234" + v.ControlPlaneSecurityGroupIDs = []string{"sg-1234"} + }, + expectedErr: "only one of vpc.securityGroup and vpc.controlPlaneSecurityGroupIDs can be specified", + }), + Entry("vpc.securityGroup set", vpcSecurityGroupEntry{ + updateVPC: func(v *api.ClusterVPC) { + v.SecurityGroup = "sg-1234" + }, + }), + Entry("vpc.controlPlaneSecurityGroupIDs set", vpcSecurityGroupEntry{ + updateVPC: func(v *api.ClusterVPC) { + v.ControlPlaneSecurityGroupIDs = []string{"sg-1234"} + }, + }), + Entry("neither set", vpcSecurityGroupEntry{ + updateVPC: func(v *api.ClusterVPC) {}, + }), + ) }) Describe("ValidatePrivateCluster", func() { diff --git a/pkg/apis/eksctl.io/v1alpha5/vpc.go b/pkg/apis/eksctl.io/v1alpha5/vpc.go index 3ed1ccdb8d..14418ea4fa 100644 --- a/pkg/apis/eksctl.io/v1alpha5/vpc.go +++ b/pkg/apis/eksctl.io/v1alpha5/vpc.go @@ -175,6 +175,12 @@ type ( // k8s API endpoint // +optional PublicAccessCIDRs []string `json:"publicAccessCIDRs,omitempty"` + // ControlPlaneSubnetIDs configures the subnets for the control plane. + // +optional + ControlPlaneSubnetIDs []string `json:"controlPlaneSubnetIDs,omitempty"` + // ControlPlaneSecurityGroupIDs configures the security groups for the control plane. + // +optional + ControlPlaneSecurityGroupIDs []string `json:"controlPlaneSecurityGroupIDs,omitempty"` } // ClusterSubnets holds private and public subnets ClusterSubnets struct { diff --git a/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go b/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go index 90ec1476ea..40c5a8092d 100644 --- a/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go +++ b/pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go @@ -724,6 +724,16 @@ func (in *ClusterVPC) DeepCopyInto(out *ClusterVPC) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.ControlPlaneSubnetIDs != nil { + in, out := &in.ControlPlaneSubnetIDs, &out.ControlPlaneSubnetIDs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ControlPlaneSecurityGroupIDs != nil { + in, out := &in.ControlPlaneSecurityGroupIDs, &out.ControlPlaneSecurityGroupIDs + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/cfn/builder/cluster.go b/pkg/cfn/builder/cluster.go index aaf0614639..a60ff70017 100644 --- a/pkg/cfn/builder/cluster.go +++ b/pkg/cfn/builder/cluster.go @@ -27,7 +27,7 @@ type ClusterResourceSet struct { ec2API awsapi.EC2 region string vpcResourceSet VPCResourceSet - securityGroups []*gfnt.Value + securityGroups *gfnt.Value } // NewClusterResourceSet returns a resource set for the new cluster. @@ -115,7 +115,13 @@ func (c *ClusterResourceSet) AddAllResources(ctx context.Context) error { func (c *ClusterResourceSet) addResourcesForSecurityGroups(vpcID *gfnt.Value) *clusterSecurityGroup { var refControlPlaneSG, refClusterSharedNodeSG *gfnt.Value - if c.spec.VPC.SecurityGroup == "" { + if sg := c.spec.VPC.SecurityGroup; sg != "" { + refControlPlaneSG = gfnt.NewString(sg) + c.securityGroups = gfnt.NewStringSlice(sg) + } else if securityGroupIDs := c.spec.VPC.ControlPlaneSecurityGroupIDs; len(securityGroupIDs) > 0 { + refControlPlaneSG = gfnt.NewString(securityGroupIDs[0]) + c.securityGroups = gfnt.NewStringSlice(securityGroupIDs...) + } else { refControlPlaneSG = c.newResource(cfnControlPlaneSGResource, &gfnec2.SecurityGroup{ GroupDescription: gfnt.NewString("Communication between the control plane and worker nodegroups"), VpcId: vpcID, @@ -146,10 +152,8 @@ func (c *ClusterResourceSet) addResourcesForSecurityGroups(vpcID *gfnt.Value) *c }) } } - } else { - refControlPlaneSG = gfnt.NewString(c.spec.VPC.SecurityGroup) + c.securityGroups = gfnt.NewSlice(refControlPlaneSG) } - c.securityGroups = []*gfnt.Value{refControlPlaneSG} // only this one SG is passed to EKS API, nodes are isolated if c.spec.VPC.SharedNodeSecurityGroup == "" { refClusterSharedNodeSG = c.newResource(cfnSharedNodeSGResource, &gfnec2.SecurityGroup{ @@ -263,12 +267,16 @@ func (c *ClusterResourceSet) newResource(name string, resource gfn.Resource) *gf func (c *ClusterResourceSet) addResourcesForControlPlane(subnetDetails *SubnetDetails) { clusterVPC := &gfneks.Cluster_ResourcesVpcConfig{ - SubnetIds: gfnt.NewSlice(subnetDetails.ControlPlaneSubnetRefs()...), EndpointPublicAccess: gfnt.NewBoolean(*c.spec.VPC.ClusterEndpoints.PublicAccess), EndpointPrivateAccess: gfnt.NewBoolean(*c.spec.VPC.ClusterEndpoints.PrivateAccess), - SecurityGroupIds: gfnt.NewSlice(c.securityGroups...), + SecurityGroupIds: c.securityGroups, PublicAccessCidrs: gfnt.NewStringSlice(c.spec.VPC.PublicAccessCIDRs...), } + if subnetIDs := c.spec.VPC.ControlPlaneSubnetIDs; len(subnetIDs) > 0 { + clusterVPC.SubnetIds = gfnt.NewStringSlice(subnetIDs...) + } else { + clusterVPC.SubnetIds = gfnt.NewSlice(subnetDetails.ControlPlaneSubnetRefs()...) + } serviceRoleARN := gfnt.MakeFnGetAttString("ServiceRole", "Arn") if api.IsSetAndNonEmptyString(c.spec.IAM.ServiceRoleARN) { diff --git a/pkg/ctl/cmdutils/update_cluster_vpc.go b/pkg/ctl/cmdutils/update_cluster_vpc.go index 2539278af5..13d546ea44 100644 --- a/pkg/ctl/cmdutils/update_cluster_vpc.go +++ b/pkg/ctl/cmdutils/update_cluster_vpc.go @@ -1,7 +1,8 @@ package cmdutils import ( - "errors" + "fmt" + "strings" "github.com/kris-nova/logger" @@ -16,6 +17,10 @@ type UpdateClusterVPCOptions struct { PublicAccess bool // PublicAccessCIDRs configures the public access CIDRs. PublicAccessCIDRs []string + // ControlPlaneSubnetIDs configures the subnets for the control plane. + ControlPlaneSubnetIDs []string + // ControlPlaneSecurityGroupIDs configures the security group IDs for the control plane. + ControlPlaneSecurityGroupIDs []string } // NewUpdateClusterVPCLoader will load config or use flags for 'eksctl utils update-cluster-vpc-config'. @@ -26,6 +31,8 @@ func NewUpdateClusterVPCLoader(cmd *Cmd, options UpdateClusterVPCOptions) Cluste "private-access", "public-access", "public-access-cidrs", + "control-plane-subnet-ids", + "control-plane-security-group-ids", } l.flagsIncompatibleWithConfigFile.Insert(supportedOptions...) @@ -42,30 +49,35 @@ func NewUpdateClusterVPCLoader(cmd *Cmd, options UpdateClusterVPCOptions) Cluste } } if !hasRequiredOptions { - return errors.New("at least one of --public-access, --private-access and --public-access-cidrs must be specified") + options := make([]string, 0, len(supportedOptions)) + for _, o := range supportedOptions { + options = append(options, "--"+o) + } + return fmt.Errorf("at least one of these options must be specified: %s", strings.Join(options, ", ")) } clusterConfig := cmd.ClusterConfig - - if clusterConfig.VPC.ClusterEndpoints == nil { - clusterConfig.VPC.ClusterEndpoints = api.ClusterEndpointAccessDefaults() - } if flag := l.CobraCommand.Flag("private-access"); flag != nil && flag.Changed { - clusterConfig.VPC.ClusterEndpoints.PrivateAccess = &options.PrivateAccess - } else { - clusterConfig.VPC.ClusterEndpoints.PrivateAccess = nil + clusterConfig.VPC.ClusterEndpoints = &api.ClusterEndpoints{ + PrivateAccess: &options.PrivateAccess, + } } - if flag := l.CobraCommand.Flag("public-access"); flag != nil && flag.Changed { - clusterConfig.VPC.ClusterEndpoints.PublicAccess = &options.PublicAccess - } else { - clusterConfig.VPC.ClusterEndpoints.PublicAccess = nil + if clusterConfig.VPC.ClusterEndpoints == nil { + clusterConfig.VPC.ClusterEndpoints = &api.ClusterEndpoints{ + PublicAccess: &options.PublicAccess, + } + } else { + clusterConfig.VPC.ClusterEndpoints.PublicAccess = &options.PublicAccess + } } clusterConfig.VPC.PublicAccessCIDRs = options.PublicAccessCIDRs + clusterConfig.VPC.ControlPlaneSubnetIDs = options.ControlPlaneSubnetIDs + clusterConfig.VPC.ControlPlaneSecurityGroupIDs = options.ControlPlaneSecurityGroupIDs return nil } l.validateWithConfigFile = func() error { - logger.Info("only changes to vpc.clusterEndpoints and vpc.publicAccessCIDRs are updated in the EKS API, changes to any other fields will be ignored") + logger.Info("only changes to vpc.clusterEndpoints, vpc.publicAccessCIDRs, vpc.controlPlaneSubnetIDs and vpc.controlPlaneSecurityGroupIDs are updated in the EKS API, changes to any other fields will be ignored") if l.ClusterConfig.VPC == nil { l.ClusterConfig.VPC = api.NewClusterVPC(false) } diff --git a/pkg/ctl/utils/mocks/VPCConfigUpdater.go b/pkg/ctl/utils/mocks/VPCConfigUpdater.go index 1cd78980b4..6500898363 100644 --- a/pkg/ctl/utils/mocks/VPCConfigUpdater.go +++ b/pkg/ctl/utils/mocks/VPCConfigUpdater.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.4. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocks diff --git a/pkg/ctl/utils/set_public_access_cidrs.go b/pkg/ctl/utils/set_public_access_cidrs.go index bbd22d8665..7154b6e8ec 100644 --- a/pkg/ctl/utils/set_public_access_cidrs.go +++ b/pkg/ctl/utils/set_public_access_cidrs.go @@ -15,6 +15,7 @@ func publicAccessCIDRsCmdWithHandler(cmd *cmdutils.Cmd, handler func(cmd *cmduti cfg := api.NewClusterConfig() cmd.ClusterConfig = cfg + cmd.CobraCommand.Deprecated = "this command is deprecated and will be removed soon. Use `eksctl utils update-cluster-vpc-config --public-access-cidrs=<> instead." cmd.SetDescription("set-public-access-cidrs", "Update public access CIDRs", "CIDR blocks that EKS uses to create a security group on the public endpoint") cmd.CobraCommand.RunE = func(_ *cobra.Command, args []string) error { @@ -56,6 +57,8 @@ func doUpdatePublicAccessCIDRs(cmd *cmdutils.Cmd) error { } cfg.VPC.ClusterEndpoints = nil + cfg.VPC.ControlPlaneSubnetIDs = nil + cfg.VPC.ControlPlaneSecurityGroupIDs = nil vpcHelper := &VPCHelper{ VPCUpdater: ctl, ClusterMeta: cfg.Metadata, diff --git a/pkg/ctl/utils/update_cluster_endpoint_access.go b/pkg/ctl/utils/update_cluster_endpoint_access.go index 39655bd5ae..66d39ed649 100644 --- a/pkg/ctl/utils/update_cluster_endpoint_access.go +++ b/pkg/ctl/utils/update_cluster_endpoint_access.go @@ -15,6 +15,7 @@ func updateClusterEndpointsCmd(cmd *cmdutils.Cmd) { cfg := api.NewClusterConfig() cmd.ClusterConfig = cfg + cmd.CobraCommand.Deprecated = "this command is deprecated and will be removed soon. Use `eksctl utils update-cluster-vpc-config --public-access=<> --private-access=<> instead." cmd.SetDescription("update-cluster-endpoints", "Update Kubernetes API endpoint access configuration", "") var ( @@ -61,6 +62,8 @@ func doUpdateClusterEndpoints(cmd *cmdutils.Cmd, newPrivate bool, newPublic bool } cfg.VPC.PublicAccessCIDRs = nil + cfg.VPC.ControlPlaneSubnetIDs = nil + cfg.VPC.ControlPlaneSecurityGroupIDs = nil vpcHelper := &VPCHelper{ VPCUpdater: ctl, ClusterMeta: cfg.Metadata, diff --git a/pkg/ctl/utils/update_cluster_vpc_config.go b/pkg/ctl/utils/update_cluster_vpc_config.go index 03334966c5..060a265bc5 100644 --- a/pkg/ctl/utils/update_cluster_vpc_config.go +++ b/pkg/ctl/utils/update_cluster_vpc_config.go @@ -46,6 +46,10 @@ func updateClusterVPCConfigWithHandler(cmd *cmdutils.Cmd, handler func(cmd *cmdu cmd.FlagSetGroup.InFlagSet("Public Access CIDRs", func(fs *pflag.FlagSet) { fs.StringSliceVar(&options.PublicAccessCIDRs, "public-access-cidrs", nil, "CIDR blocks that EKS uses to create a security group on the public endpoint") }) + cmd.FlagSetGroup.InFlagSet("Control plane subnets and security groups", func(fs *pflag.FlagSet) { + fs.StringSliceVar(&options.ControlPlaneSubnetIDs, "control-plane-subnet-ids", nil, "Subnet IDs for the control plane") + fs.StringSliceVar(&options.ControlPlaneSecurityGroupIDs, "control-plane-security-group-ids", nil, "Security group IDs for the control plane") + }) cmdutils.AddCommonFlagsForAWS(cmd, &cmd.ProviderConfig, false) } diff --git a/pkg/ctl/utils/update_cluster_vpc_config_test.go b/pkg/ctl/utils/update_cluster_vpc_config_test.go index 5a616a8115..3733b1c9fb 100644 --- a/pkg/ctl/utils/update_cluster_vpc_config_test.go +++ b/pkg/ctl/utils/update_cluster_vpc_config_test.go @@ -21,6 +21,6 @@ var _ = DescribeTable("invalid usage of update-cluster-vpc-config", func(e updat Entry("missing a required parameter", updateClusterVPCEntry{ args: []string{"--cluster", "test"}, - expectedErr: "at least one of --public-access, --private-access and --public-access-cidrs must be specified", + expectedErr: "at least one of these options must be specified: --private-access, --public-access, --public-access-cidrs, --control-plane-subnet-ids, --control-plane-security-group-ids", }), ) diff --git a/pkg/ctl/utils/vpc_helper.go b/pkg/ctl/utils/vpc_helper.go index bebf549d40..2155d0d40f 100644 --- a/pkg/ctl/utils/vpc_helper.go +++ b/pkg/ctl/utils/vpc_helper.go @@ -7,7 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/kris-nova/logger" - "k8s.io/apimachinery/pkg/util/sets" + "golang.org/x/exp/slices" "github.com/aws/aws-sdk-go-v2/service/eks" ekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types" @@ -48,10 +48,57 @@ func (v *VPCHelper) UpdateClusterVPCConfig(ctx context.Context, vpc *api.Cluster return err } } + if vpc.ControlPlaneSubnetIDs != nil || vpc.ControlPlaneSecurityGroupIDs != nil { + if err := v.updateSubnetsSecurityGroups(ctx, vpc); err != nil { + return err + } + } cmdutils.LogPlanModeWarning(v.PlanMode) return nil } +func (v *VPCHelper) updateSubnetsSecurityGroups(ctx context.Context, vpc *api.ClusterVPC) error { + current := v.Cluster.ResourcesVpcConfig + hasUpdate := false + vpcUpdate := &ekstypes.VpcConfigRequest{ + SubnetIds: current.SubnetIds, + SecurityGroupIds: current.SecurityGroupIds, + } + + compareValues := func(currentValues, newValues []string, resourceName string, updateFn func()) { + if !slices.Equal(currentValues, newValues) { + updateFn() + hasUpdate = true + cmdutils.LogIntendedAction(v.PlanMode, "update %s for cluster %q in %q to: %v", resourceName, + v.ClusterMeta.Name, v.ClusterMeta.Region, newValues) + } else { + logger.Success("%s for cluster %q in %q are already up-to-date", resourceName, v.ClusterMeta.Name, v.ClusterMeta.Region) + } + } + if vpc.ControlPlaneSubnetIDs != nil { + compareValues(current.SubnetIds, vpc.ControlPlaneSubnetIDs, "control plane subnet IDs", func() { + vpcUpdate.SubnetIds = vpc.ControlPlaneSubnetIDs + }) + } + + if vpc.ControlPlaneSecurityGroupIDs != nil { + compareValues(current.SecurityGroupIds, vpc.ControlPlaneSecurityGroupIDs, "control plane security group IDs", func() { + vpcUpdate.SecurityGroupIds = vpc.ControlPlaneSecurityGroupIDs + }) + } + + if v.PlanMode || !hasUpdate { + return nil + } + if err := v.updateVPCConfig(ctx, vpcUpdate); err != nil { + return err + } + cmdutils.LogCompletedAction(false, "control plane subnets and security groups for cluster %q in %q have been updated to: "+ + "controlPlaneSubnetIDs=%v, controlPlaneSecurityGroupIDs=%v", v.ClusterMeta.Name, v.ClusterMeta.Region, vpcUpdate.SubnetIds, vpcUpdate.SecurityGroupIds) + + return nil +} + func (v *VPCHelper) updateEndpointAccess(ctx context.Context, desired api.ClusterEndpoints) error { current := v.Cluster.ResourcesVpcConfig if desired.PublicAccess == nil { @@ -129,5 +176,5 @@ func cidrsEqual(currentValues, newValues []string) bool { if len(newValues) == 0 && len(currentValues) == 1 && currentValues[0] == "0.0.0.0/0" { return true } - return sets.NewString(currentValues...).Equal(sets.NewString(newValues...)) + return slices.Equal(currentValues, newValues) } diff --git a/pkg/ctl/utils/vpc_helper_test.go b/pkg/ctl/utils/vpc_helper_test.go index cd66052e7c..cbcca769b4 100644 --- a/pkg/ctl/utils/vpc_helper_test.go +++ b/pkg/ctl/utils/vpc_helper_test.go @@ -197,4 +197,148 @@ var _ = DescribeTable("VPCHelper", func(e vpcHelperEntry) { expectedErr: "this operation is not supported on Outposts clusters", }), + + Entry("cluster matches desired config when subnets and security groups are specified", vpcHelperEntry{ + clusterVPC: &ekstypes.VpcConfigResponse{ + EndpointPublicAccess: true, + EndpointPrivateAccess: false, + PublicAccessCidrs: []string{"0.0.0.0/0"}, + SecurityGroupIds: []string{"sg-1234"}, + SubnetIds: []string{"subnet-1234"}, + }, + vpc: &api.ClusterVPC{ + ClusterEndpoints: &api.ClusterEndpoints{ + PublicAccess: api.Enabled(), + PrivateAccess: api.Disabled(), + }, + ControlPlaneSecurityGroupIDs: []string{"sg-1234"}, + ControlPlaneSubnetIDs: []string{"subnet-1234"}, + }, + }), + + Entry("cluster security groups do not match desired config", vpcHelperEntry{ + clusterVPC: &ekstypes.VpcConfigResponse{ + EndpointPublicAccess: true, + EndpointPrivateAccess: false, + PublicAccessCidrs: []string{"0.0.0.0/0"}, + SecurityGroupIds: []string{"sg-1234"}, + SubnetIds: []string{"subnet-1234"}, + }, + vpc: &api.ClusterVPC{ + ClusterEndpoints: &api.ClusterEndpoints{ + PublicAccess: api.Enabled(), + PrivateAccess: api.Disabled(), + }, + ControlPlaneSecurityGroupIDs: []string{"sg-1234", "sg-5678"}, + ControlPlaneSubnetIDs: []string{"subnet-1234"}, + }, + + expectedUpdates: []*eks.UpdateClusterConfigInput{ + { + Name: aws.String("test"), + ResourcesVpcConfig: &ekstypes.VpcConfigRequest{ + SecurityGroupIds: []string{"sg-1234", "sg-5678"}, + SubnetIds: []string{"subnet-1234"}, + }, + }, + }, + }), + + Entry("cluster subnets do not match desired config", vpcHelperEntry{ + clusterVPC: &ekstypes.VpcConfigResponse{ + EndpointPublicAccess: true, + EndpointPrivateAccess: false, + PublicAccessCidrs: []string{"0.0.0.0/0"}, + SecurityGroupIds: []string{"sg-1234", "sg-5678"}, + SubnetIds: []string{"subnet-1234"}, + }, + vpc: &api.ClusterVPC{ + ClusterEndpoints: &api.ClusterEndpoints{ + PublicAccess: api.Enabled(), + PrivateAccess: api.Disabled(), + }, + ControlPlaneSecurityGroupIDs: []string{"sg-1234", "sg-5678"}, + ControlPlaneSubnetIDs: []string{"subnet-1234", "subnet-5678"}, + }, + + expectedUpdates: []*eks.UpdateClusterConfigInput{ + { + Name: aws.String("test"), + ResourcesVpcConfig: &ekstypes.VpcConfigRequest{ + SecurityGroupIds: []string{"sg-1234", "sg-5678"}, + SubnetIds: []string{"subnet-1234", "subnet-5678"}, + }, + }, + }, + }), + + Entry("cluster security group and subnets do not match desired config", vpcHelperEntry{ + clusterVPC: &ekstypes.VpcConfigResponse{ + EndpointPublicAccess: true, + EndpointPrivateAccess: false, + PublicAccessCidrs: []string{"0.0.0.0/0"}, + SecurityGroupIds: []string{"sg-1234", "sg-5678"}, + SubnetIds: []string{"subnet-1234"}, + }, + vpc: &api.ClusterVPC{ + ClusterEndpoints: &api.ClusterEndpoints{ + PublicAccess: api.Enabled(), + PrivateAccess: api.Disabled(), + }, + ControlPlaneSecurityGroupIDs: []string{"sg-1234", "sg-5678"}, + ControlPlaneSubnetIDs: []string{"subnet-1234", "subnet-5678"}, + }, + + expectedUpdates: []*eks.UpdateClusterConfigInput{ + { + Name: aws.String("test"), + ResourcesVpcConfig: &ekstypes.VpcConfigRequest{ + SecurityGroupIds: []string{"sg-1234", "sg-5678"}, + SubnetIds: []string{"subnet-1234", "subnet-5678"}, + }, + }, + }, + }), + + Entry("no fields match desired config", vpcHelperEntry{ + clusterVPC: &ekstypes.VpcConfigResponse{ + EndpointPublicAccess: false, + EndpointPrivateAccess: true, + PublicAccessCidrs: []string{"0.0.0.0/0"}, + SecurityGroupIds: []string{"sg-1234"}, + SubnetIds: []string{"subnet-1234"}, + }, + vpc: &api.ClusterVPC{ + ClusterEndpoints: &api.ClusterEndpoints{ + PublicAccess: api.Enabled(), + PrivateAccess: api.Disabled(), + }, + PublicAccessCIDRs: []string{"1.1.1.1/1"}, + ControlPlaneSecurityGroupIDs: []string{"sg-1234", "sg-5678"}, + ControlPlaneSubnetIDs: []string{"subnet-1234", "subnet-5678"}, + }, + + expectedUpdates: []*eks.UpdateClusterConfigInput{ + { + Name: aws.String("test"), + ResourcesVpcConfig: &ekstypes.VpcConfigRequest{ + EndpointPublicAccess: api.Enabled(), + EndpointPrivateAccess: api.Disabled(), + }, + }, + { + Name: aws.String("test"), + ResourcesVpcConfig: &ekstypes.VpcConfigRequest{ + PublicAccessCidrs: []string{"1.1.1.1/1"}, + }, + }, + { + Name: aws.String("test"), + ResourcesVpcConfig: &ekstypes.VpcConfigRequest{ + SecurityGroupIds: []string{"sg-1234", "sg-5678"}, + SubnetIds: []string{"subnet-1234", "subnet-5678"}, + }, + }, + }, + }), ) diff --git a/pkg/eks/mocks/ConfigProvider.go b/pkg/eks/mocks/ConfigProvider.go index 5e809b75b7..4d355e02d5 100644 --- a/pkg/eks/mocks/ConfigProvider.go +++ b/pkg/eks/mocks/ConfigProvider.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocks diff --git a/pkg/eks/mocks/KubeNodeGroup.go b/pkg/eks/mocks/KubeNodeGroup.go index 38f1266e8d..838077b6c1 100644 --- a/pkg/eks/mocks/KubeNodeGroup.go +++ b/pkg/eks/mocks/KubeNodeGroup.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocks diff --git a/pkg/eks/mocksv2/ASG.go b/pkg/eks/mocksv2/ASG.go index a349b2c35c..7e0e7517a0 100644 --- a/pkg/eks/mocksv2/ASG.go +++ b/pkg/eks/mocksv2/ASG.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/CloudFormation.go b/pkg/eks/mocksv2/CloudFormation.go index 2fedae3a33..c166d54efd 100644 --- a/pkg/eks/mocksv2/CloudFormation.go +++ b/pkg/eks/mocksv2/CloudFormation.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/CloudTrail.go b/pkg/eks/mocksv2/CloudTrail.go index e42d9037fe..b36b3eb52e 100644 --- a/pkg/eks/mocksv2/CloudTrail.go +++ b/pkg/eks/mocksv2/CloudTrail.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/CloudWatchLogs.go b/pkg/eks/mocksv2/CloudWatchLogs.go index 2704574faf..eb560e8951 100644 --- a/pkg/eks/mocksv2/CloudWatchLogs.go +++ b/pkg/eks/mocksv2/CloudWatchLogs.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/CredentialsProvider.go b/pkg/eks/mocksv2/CredentialsProvider.go index e90646c8a4..ecfad51e3a 100644 --- a/pkg/eks/mocksv2/CredentialsProvider.go +++ b/pkg/eks/mocksv2/CredentialsProvider.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/EC2.go b/pkg/eks/mocksv2/EC2.go index 2da8e309f6..c3d7bf155a 100644 --- a/pkg/eks/mocksv2/EC2.go +++ b/pkg/eks/mocksv2/EC2.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/EKS.go b/pkg/eks/mocksv2/EKS.go index ac9e59ea7f..621beea3ab 100644 --- a/pkg/eks/mocksv2/EKS.go +++ b/pkg/eks/mocksv2/EKS.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/ELB.go b/pkg/eks/mocksv2/ELB.go index 83d8a2da97..b931191739 100644 --- a/pkg/eks/mocksv2/ELB.go +++ b/pkg/eks/mocksv2/ELB.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/ELBV2.go b/pkg/eks/mocksv2/ELBV2.go index 02fa44db67..0a0a3d0bb9 100644 --- a/pkg/eks/mocksv2/ELBV2.go +++ b/pkg/eks/mocksv2/ELBV2.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/IAM.go b/pkg/eks/mocksv2/IAM.go index d335605596..b631faf946 100644 --- a/pkg/eks/mocksv2/IAM.go +++ b/pkg/eks/mocksv2/IAM.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/Outposts.go b/pkg/eks/mocksv2/Outposts.go index 76c2509631..63653daa51 100644 --- a/pkg/eks/mocksv2/Outposts.go +++ b/pkg/eks/mocksv2/Outposts.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/SSM.go b/pkg/eks/mocksv2/SSM.go index 3d3f15d52a..e84a86c534 100644 --- a/pkg/eks/mocksv2/SSM.go +++ b/pkg/eks/mocksv2/SSM.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/pkg/eks/mocksv2/STS.go b/pkg/eks/mocksv2/STS.go index 98f66be61c..dbb2684aec 100644 --- a/pkg/eks/mocksv2/STS.go +++ b/pkg/eks/mocksv2/STS.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.32.2. DO NOT EDIT. +// Code generated by mockery v2.33.1. DO NOT EDIT. package mocksv2 diff --git a/userdocs/mkdocs.yml b/userdocs/mkdocs.yml index 66ac005bfa..6ce84d4a44 100644 --- a/userdocs/mkdocs.yml +++ b/userdocs/mkdocs.yml @@ -179,6 +179,7 @@ nav: - usage/vpc-configuration.md - usage/vpc-subnet-settings.md - usage/vpc-cluster-access.md + - usage/cluster-subnets-security-groups.md - usage/vpc-ip-family.md - IAM: - usage/minimum-iam-policies.md diff --git a/userdocs/src/getting-started.md b/userdocs/src/getting-started.md index 8c055fa2d5..fefb363dfa 100644 --- a/userdocs/src/getting-started.md +++ b/userdocs/src/getting-started.md @@ -1,6 +1,8 @@ # Getting started !!! tip "New for 2023" + `eksctl` now supports [updating the subnets and security groups](/usage/cluster-subnets-security-groups) associated with the EKS control plane. + `eksctl` now supports creating fully private clusters on [AWS Outposts](/usage/outposts). `eksctl` now supports new ISO regions `us-iso-east-1` and `us-isob-east-1`. diff --git a/userdocs/src/usage/cluster-subnets-security-groups.md b/userdocs/src/usage/cluster-subnets-security-groups.md new file mode 100644 index 0000000000..1fb83cf135 --- /dev/null +++ b/userdocs/src/usage/cluster-subnets-security-groups.md @@ -0,0 +1,83 @@ +# Updating control plane subnets and security groups + +## Updating control plane subnets +When a cluster is created with eksctl, a set of public and private subnets are created and passed to the EKS API. +EKS creates 2 to 4 cross-account elastic network interfaces (ENIs) in those subnets to enable communication between the EKS +managed Kubernetes control plane and your VPC. + +To update the subnets used by the EKS control plane, run: + +```console +eksctl utils update-cluster-vpc-config --cluster= --control-plane-subnet-ids=subnet-1234,subnet-5678 +``` + +To update the setting using a config file: + +```yaml +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig +metadata: + name: cluster + region: us-west-2 + +vpc: + controlPlaneSubnetIDs: [subnet-1234, subnet-5678] +``` + +```console +eksctl utils update-cluster-vpc-config -f config.yaml +``` + +Without the `--approve` flag, eksctl only logs the proposed changes. Once you are satisfied with the proposed changes, rerun the command with +the `--approve` flag. + +## Updating control plane security groups +To manage traffic between the control plane and worker nodes, EKS supports passing additional security groups that are applied to the cross-account network interfaces +provisioned by EKS. To update the security groups for the EKS control plane, run: + +```console +eksctl utils update-cluster-vpc-config --cluster= --control-plane-security-group-ids=sg-1234,sg-5678 --approve +``` + +To update the setting using a config file: + +```yaml +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig +metadata: + name: cluster + region: us-west-2 + +vpc: + controlPlaneSecurityGroupIDs: [sg-1234, sg-5678] +``` + +```console +eksctl utils update-cluster-vpc-config -f config.yaml +``` + +To update both control plane subnets and security groups for a cluster, run: + +```console +eksctl utils update-cluster-vpc-config --cluster= --control-plane-subnet-ids=<> --control-plane-security-group-ids=<> --approve +``` + +To update both fields using a config file: + +```yaml +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig +metadata: + name: cluster + region: us-west-2 + +vpc: + controlPlaneSubnetIDs: [subnet-1234, subnet-5678] + controlPlaneSecurityGroupIDs: [sg-1234, sg-5678] +``` + +```console +eksctl utils update-cluster-vpc-config -f config.yaml +``` + +For a complete example, refer to [https://github.com/eksctl-io/eksctl/blob/main/examples/38-cluster-subnets-sgs.yaml](cluster-subnets-sgs.yaml). \ No newline at end of file diff --git a/userdocs/src/usage/vpc-cluster-access.md b/userdocs/src/usage/vpc-cluster-access.md index 128757c2e7..2a9fec28dc 100644 --- a/userdocs/src/usage/vpc-cluster-access.md +++ b/userdocs/src/usage/vpc-cluster-access.md @@ -33,14 +33,18 @@ There are some additional caveats when configuring Kubernetes API endpoint acces The following is an example of how one could configure the Kubernetes API endpoint access using the `utils` sub-command: +```console +eksctl utils update-cluster-vpc-config --cluster= --private-access=true --public-access=false ``` -eksctl utils update-cluster-endpoints --name= --private-access=true --public-access=false -``` + +!!! warning + `eksctl utils update-cluster-endpoints` has been deprecated in favour of `eksctl utils update-cluster-vpc-config` + and will be removed soon. To update the setting using a `ClusterConfig` file, use: ```console -eksctl utils update-cluster-endpoints -f config.yaml --approve +eksctl utils update-cluster-vpc-config -f config.yaml --approve ``` Note that if you don't pass a flag, it will keep the current value. Once you are satisfied with the proposed changes, @@ -59,13 +63,17 @@ vpc: To update the restrictions on an existing cluster, use: ```console -eksctl utils set-public-access-cidrs --cluster= 1.1.1.1/32,2.2.2.0/24 +eksctl utils update-cluster-vpc-config --cluster= 1.1.1.1/32,2.2.2.0/24 ``` +!!! warning + `eksctl utils set-public-access-cidrs` has been deprecated in favour of `eksctl utils update-cluster-vpc-config` + and will be removed soon. + To update the restrictions using a `ClusterConfig` file, set the new CIDRs in `vpc.publicAccessCIDRs` and run: ```console -eksctl utils set-public-access-cidrs -f config.yaml +eksctl utils update-cluster-vpc-config -f config.yaml ``` !!! warning @@ -81,3 +89,24 @@ eksctl utils set-public-access-cidrs -f config.yaml the internet. (Source: https://github.com/aws/containers-roadmap/issues/108#issuecomment-552766489) Implementation notes: https://github.com/aws/containers-roadmap/issues/108#issuecomment-552698875 + + +To update both API server endpoint access and public access CIDRs for a cluster in a single command, run: + +```console +eksctl utils update-cluster-vpc-config --cluster= --public-access=true --private-access=true --public-access-cidrs=1.1.1.1/32,2.2.2.0/24 +``` + +To update the setting using a config file: + +```yaml +vpc: + clusterEndpoints: + publicAccess: + privateAccess: + publicAccessCIDRs: ["1.1.1.1/32"] +``` + +```console +eksctl utils update-cluster-vpc-config --cluster= -f config.yaml +```