diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml index 7ab077b658..7bd19182fe 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml @@ -2832,6 +2832,11 @@ spec: region: description: The AWS Region the cluster lives in. type: string + restrictPrivateSubnets: + default: false + description: RestrictPrivateSubnets indicates that the EKS control + plane should only use private subnets. + type: boolean roleAdditionalPolicies: description: |- RoleAdditionalPolicies allows you to attach additional polices to diff --git a/controlplane/eks/api/v1beta1/conversion.go b/controlplane/eks/api/v1beta1/conversion.go index 57284afd25..e137e7dede 100644 --- a/controlplane/eks/api/v1beta1/conversion.go +++ b/controlplane/eks/api/v1beta1/conversion.go @@ -40,6 +40,7 @@ func (r *AWSManagedControlPlane) ConvertTo(dstRaw conversion.Hub) error { } dst.Spec.VpcCni.Disable = r.Spec.DisableVPCCNI dst.Spec.Partition = restored.Spec.Partition + dst.Spec.RestrictPrivateSubnets = restored.Spec.RestrictPrivateSubnets return nil } diff --git a/controlplane/eks/api/v1beta1/zz_generated.conversion.go b/controlplane/eks/api/v1beta1/zz_generated.conversion.go index ecc37543d6..151772f75b 100644 --- a/controlplane/eks/api/v1beta1/zz_generated.conversion.go +++ b/controlplane/eks/api/v1beta1/zz_generated.conversion.go @@ -393,6 +393,7 @@ func autoConvert_v1beta2_AWSManagedControlPlaneSpec_To_v1beta1_AWSManagedControl if err := Convert_v1beta2_VpcCni_To_v1beta1_VpcCni(&in.VpcCni, &out.VpcCni, s); err != nil { return err } + // WARNING: in.RestrictPrivateSubnets requires manual conversion: does not exist in peer-type if err := Convert_v1beta2_KubeProxy_To_v1beta1_KubeProxy(&in.KubeProxy, &out.KubeProxy, s); err != nil { return err } diff --git a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go index fa96f494d8..109752e573 100644 --- a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go +++ b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go @@ -173,6 +173,10 @@ type AWSManagedControlPlaneSpec struct { //nolint: maligned // +optional VpcCni VpcCni `json:"vpcCni,omitempty"` + // RestrictPrivateSubnets indicates that the EKS control plane should only use private subnets. + // +kubebuilder:default=false + RestrictPrivateSubnets bool `json:"restrictPrivateSubnets,omitempty"` + // KubeProxy defines managed attributes of the kube-proxy daemonset KubeProxy KubeProxy `json:"kubeProxy,omitempty"` } diff --git a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go index 4b44508b65..92865aa342 100644 --- a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go +++ b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_webhook.go @@ -91,6 +91,7 @@ func (r *AWSManagedControlPlane) ValidateCreate() (admission.Warnings, error) { allErrs = append(allErrs, r.validateSecondaryCIDR()...) allErrs = append(allErrs, r.validateEKSAddons()...) allErrs = append(allErrs, r.validateDisableVPCCNI()...) + allErrs = append(allErrs, r.validateRestrictPrivateSubnets()...) allErrs = append(allErrs, r.validateKubeProxy()...) allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...) allErrs = append(allErrs, r.validateNetwork()...) @@ -126,6 +127,7 @@ func (r *AWSManagedControlPlane) ValidateUpdate(old runtime.Object) (admission.W allErrs = append(allErrs, r.validateSecondaryCIDR()...) allErrs = append(allErrs, r.validateEKSAddons()...) allErrs = append(allErrs, r.validateDisableVPCCNI()...) + allErrs = append(allErrs, r.validateRestrictPrivateSubnets()...) allErrs = append(allErrs, r.validateKubeProxy()...) allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...) allErrs = append(allErrs, r.validatePrivateDNSHostnameTypeOnLaunch()...) @@ -392,6 +394,22 @@ func (r *AWSManagedControlPlane) validateDisableVPCCNI() field.ErrorList { return allErrs } +func (r *AWSManagedControlPlane) validateRestrictPrivateSubnets() field.ErrorList { + var allErrs field.ErrorList + + if r.Spec.RestrictPrivateSubnets && r.Spec.NetworkSpec.VPC.IsUnmanaged(r.Spec.EKSClusterName) { + boolField := field.NewPath("spec", "restrictPrivateSubnets") + if len(r.Spec.NetworkSpec.Subnets.FilterPrivate()) == 0 { + allErrs = append(allErrs, field.Invalid(boolField, r.Spec.RestrictPrivateSubnets, "cannot enable private subnets restriction when no private subnets are specified")) + } + } + + if len(allErrs) == 0 { + return nil + } + return allErrs +} + func (r *AWSManagedControlPlane) validatePrivateDNSHostnameTypeOnLaunch() field.ErrorList { var allErrs field.ErrorList diff --git a/pkg/cloud/scope/managedcontrolplane.go b/pkg/cloud/scope/managedcontrolplane.go index 018cbc7781..283dec5798 100644 --- a/pkg/cloud/scope/managedcontrolplane.go +++ b/pkg/cloud/scope/managedcontrolplane.go @@ -407,6 +407,11 @@ func (s *ManagedControlPlaneScope) VpcCni() ekscontrolplanev1.VpcCni { return s.ControlPlane.Spec.VpcCni } +// RestrictPrivateSubnets returns whether Control Plane should be restricted to Private subnets. +func (s *ManagedControlPlaneScope) RestrictPrivateSubnets() bool { + return s.ControlPlane.Spec.RestrictPrivateSubnets +} + // OIDCIdentityProviderConfig returns the OIDC identity provider config. func (s *ManagedControlPlaneScope) OIDCIdentityProviderConfig() *ekscontrolplanev1.OIDCIdentityProviderConfig { return s.ControlPlane.Spec.OIDCIdentityProviderConfig diff --git a/pkg/cloud/services/eks/cluster.go b/pkg/cloud/services/eks/cluster.go index aeeda95ba4..62c990bd36 100644 --- a/pkg/cloud/services/eks/cluster.go +++ b/pkg/cloud/services/eks/cluster.go @@ -359,9 +359,18 @@ func makeEksLogging(loggingSpec *ekscontrolplanev1.ControlPlaneLoggingSpec) *eks } func (s *Service) createCluster(eksClusterName string) (*eks.Cluster, error) { + var ( + vpcConfig *eks.VpcConfigRequest + err error + ) logging := makeEksLogging(s.scope.ControlPlane.Spec.Logging) encryptionConfigs := makeEksEncryptionConfigs(s.scope.ControlPlane.Spec.EncryptionConfig) - vpcConfig, err := makeVpcConfig(s.scope.Subnets(), s.scope.ControlPlane.Spec.EndpointAccess, s.scope.SecurityGroups()) + if s.scope.ControlPlane.Spec.RestrictPrivateSubnets { + s.scope.Info("Filtering private subnets") + vpcConfig, err = makeVpcConfig(s.scope.Subnets().FilterPrivate(), s.scope.ControlPlane.Spec.EndpointAccess, s.scope.SecurityGroups()) + } else { + vpcConfig, err = makeVpcConfig(s.scope.Subnets(), s.scope.ControlPlane.Spec.EndpointAccess, s.scope.SecurityGroups()) + } if err != nil { return nil, errors.Wrap(err, "couldn't create vpc config for cluster") } @@ -542,8 +551,16 @@ func publicAccessCIDRsEqual(as []*string, bs []*string) bool { } func (s *Service) reconcileVpcConfig(vpcConfig *eks.VpcConfigResponse) (*eks.VpcConfigRequest, error) { + var ( + updatedVpcConfig *eks.VpcConfigRequest + err error + ) endpointAccess := s.scope.ControlPlane.Spec.EndpointAccess - updatedVpcConfig, err := makeVpcConfig(s.scope.Subnets(), endpointAccess, s.scope.SecurityGroups()) + if s.scope.ControlPlane.Spec.RestrictPrivateSubnets { + updatedVpcConfig, err = makeVpcConfig(s.scope.Subnets().FilterPrivate(), endpointAccess, s.scope.SecurityGroups()) + } else { + updatedVpcConfig, err = makeVpcConfig(s.scope.Subnets(), endpointAccess, s.scope.SecurityGroups()) + } if err != nil { return nil, err }