Skip to content

Commit

Permalink
Merge pull request eksctl-io#7218 from cPu1/cluster-subnets-security-…
Browse files Browse the repository at this point in the history
…groups

Allow mutating control plane subnets and security groups
  • Loading branch information
cPu1 authored Oct 24, 2023
2 parents dd6426c + c2c91ab commit 72605fb
Show file tree
Hide file tree
Showing 35 changed files with 582 additions and 46 deletions.
22 changes: 22 additions & 0 deletions examples/38-cluster-subnets-sgs.yaml
Original file line number Diff line number Diff line change
@@ -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
105 changes: 105 additions & 0 deletions integration/tests/managed/managed_nodegroup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
)
Expand Down Expand Up @@ -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() {
Expand Down
20 changes: 19 additions & 1 deletion pkg/apis/eksctl.io/v1alpha5/assets/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href=\"/usage/vpc-networking/#managing-access-to-the-kubernetes-api-server-endpoints\">managing access to API</a>"
},
"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"
Expand Down Expand Up @@ -733,7 +749,9 @@
"autoAllocateIPv6",
"nat",
"clusterEndpoints",
"publicAccessCIDRs"
"publicAccessCIDRs",
"controlPlaneSubnetIDs",
"controlPlaneSecurityGroupIDs"
],
"additionalProperties": false,
"description": "holds global subnet and all child subnets",
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
35 changes: 35 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 10 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 15 additions & 7 deletions pkg/cfn/builder/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 72605fb

Please sign in to comment.