From 8e9df3ee22ac707d3de36f36534f3765d0e5f76c Mon Sep 17 00:00:00 2001 From: Adam Malcontenti-Wilson Date: Tue, 27 Aug 2024 17:15:02 +1000 Subject: [PATCH] feat: support setting EKS AuthenticationMode --- .../v1beta2/awsmanagedcontrolplane_types.go | 15 +++++- controlplane/eks/api/v1beta2/types.go | 15 ++++++ pkg/cloud/services/eks/cluster.go | 54 +++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go index 109752e573..cf114fb230 100644 --- a/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go +++ b/controlplane/eks/api/v1beta2/awsmanagedcontrolplane_types.go @@ -164,11 +164,15 @@ type AWSManagedControlPlaneSpec struct { //nolint: maligned // +optional Addons *[]Addon `json:"addons,omitempty"` - // IdentityProviderconfig is used to specify the oidc provider config + // OIDCIdentityProviderConfig is used to specify the oidc provider config // to be attached with this eks cluster // +optional OIDCIdentityProviderConfig *OIDCIdentityProviderConfig `json:"oidcIdentityProviderConfig,omitempty"` + // AccessConfig specifies the access configuration information for the cluster + // +optional + AccessConfig *AccessConfig `json:"accessConfig,omitempty"` + // VpcCni is used to set configuration options for the VPC CNI plugin // +optional VpcCni VpcCni `json:"vpcCni,omitempty"` @@ -219,6 +223,15 @@ type EndpointAccess struct { Private *bool `json:"private,omitempty"` } +// AccessConfig represents the access configuration information for the cluster +type AccessConfig struct { + // AuthenticationMode specifies the desired authentication mode for the cluster + // Defaults to CONFIG_MAP + // +kubebuilder:default=CONFIG_MAP + // +kubebuilder:validation:Enum=CONFIG_MAP;API;API_AND_CONFIG_MAP + AuthenticationMode EKSAuthenticationMode `json:"authenticationMode,omitempty"` +} + // EncryptionConfig specifies the encryption configuration for the EKS clsuter. type EncryptionConfig struct { // Provider specifies the ARN or alias of the CMK (in AWS KMS) diff --git a/controlplane/eks/api/v1beta2/types.go b/controlplane/eks/api/v1beta2/types.go index 1ef47215ce..6f385f7d40 100644 --- a/controlplane/eks/api/v1beta2/types.go +++ b/controlplane/eks/api/v1beta2/types.go @@ -79,6 +79,21 @@ var ( EKSTokenMethodAWSCli = EKSTokenMethod("aws-cli") ) +// EKSAuthenticationMode defines the authentication mode for the cluster +type EKSAuthenticationMode string + +var ( + // EKSAuthenticationModeConfigMap indicates that only `aws-auth` ConfigMap will be used for authentication + EKSAuthenticationModeConfigMap = EKSAuthenticationMode("CONFIG_MAP") + + // EKSAuthenticationModeApi indicates that only AWS Access Entries will be used for authentication + EKSAuthenticationModeApi = EKSAuthenticationMode("API") + + // EKSAuthenticationModeApiAndConfigMap indicates that both `aws-auth` ConfigMap and AWS Access Entries will + // be used for authentication + EKSAuthenticationModeApiAndConfigMap = EKSAuthenticationMode("API_AND_CONFIG_MAP") +) + var ( // DefaultEKSControlPlaneRole is the name of the default IAM role to use for the EKS control plane // if no other role is supplied in the spec and if iam role creation is not enabled. The default diff --git a/pkg/cloud/services/eks/cluster.go b/pkg/cloud/services/eks/cluster.go index 62c990bd36..82c3a2f925 100644 --- a/pkg/cloud/services/eks/cluster.go +++ b/pkg/cloud/services/eks/cluster.go @@ -121,6 +121,10 @@ func (s *Service) reconcileCluster(ctx context.Context) error { return errors.Wrap(err, "failed reconciling cluster config") } + if err := s.reconcileAccessConfig(cluster.AccessConfig); err != nil { + return errors.Wrap(err, "failed reconciling access config") + } + if err := s.reconcileLogging(cluster.Logging); err != nil { return errors.Wrap(err, "failed reconciling logging") } @@ -375,6 +379,13 @@ func (s *Service) createCluster(eksClusterName string) (*eks.Cluster, error) { return nil, errors.Wrap(err, "couldn't create vpc config for cluster") } + var accessConfig *eks.CreateAccessConfigRequest + if s.scope.ControlPlane.Spec.AccessConfig != nil && s.scope.ControlPlane.Spec.AccessConfig.AuthenticationMode != "" { + accessConfig = &eks.CreateAccessConfigRequest{ + AuthenticationMode: aws.String(string(s.scope.ControlPlane.Spec.AccessConfig.AuthenticationMode)), + } + } + var netConfig *eks.KubernetesNetworkConfigRequest if s.scope.VPC().IsIPv6Enabled() { netConfig = &eks.KubernetesNetworkConfigRequest{ @@ -416,6 +427,7 @@ func (s *Service) createCluster(eksClusterName string) (*eks.Cluster, error) { Name: aws.String(eksClusterName), Version: eksVersion, Logging: logging, + AccessConfig: accessConfig, EncryptionConfig: encryptionConfigs, ResourcesVpcConfig: vpcConfig, RoleArn: role.Arn, @@ -423,6 +435,10 @@ func (s *Service) createCluster(eksClusterName string) (*eks.Cluster, error) { KubernetesNetworkConfig: netConfig, } + if err := input.Validate(); err != nil { + return nil, errors.Wrap(err, "created invalid CreateClusterInput") + } + var out *eks.CreateClusterOutput if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { if out, err = s.EKSClient.CreateCluster(input); err != nil { @@ -501,6 +517,44 @@ func (s *Service) reconcileClusterConfig(cluster *eks.Cluster) error { return nil } +func (s *Service) reconcileAccessConfig(accessConfig *eks.AccessConfigResponse) error { + input := eks.UpdateClusterConfigInput{Name: aws.String(s.scope.KubernetesClusterName())} + + if s.scope.ControlPlane.Spec.AccessConfig == nil || s.scope.ControlPlane.Spec.AccessConfig.AuthenticationMode == "" { + return nil + } + + expectedAuthenticationMode := string(s.scope.ControlPlane.Spec.AccessConfig.AuthenticationMode) + if expectedAuthenticationMode != aws.StringValue(accessConfig.AuthenticationMode) { + input.AccessConfig = &eks.UpdateAccessConfigRequest{ + AuthenticationMode: aws.String(expectedAuthenticationMode), + } + } + + if input.AccessConfig != nil { + if err := input.Validate(); err != nil { + return errors.Wrap(err, "created invalid UpdateClusterConfigInput") + } + + if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) { + if _, err := s.EKSClient.UpdateClusterConfig(&input); err != nil { + if aerr, ok := err.(awserr.Error); ok { + return false, aerr + } + return false, err + } + conditions.MarkTrue(s.scope.ControlPlane, ekscontrolplanev1.EKSControlPlaneUpdatingCondition) + record.Eventf(s.scope.ControlPlane, "InitiatedUpdateEKSControlPlane", "Initiated auth config update for EKS control plane %s", s.scope.KubernetesClusterName()) + return true, nil + }); err != nil { + record.Warnf(s.scope.ControlPlane, "FailedUpdateEKSControlPlane", "Failed to update EKS control plane auth config: %v", err) + return errors.Wrapf(err, "failed to update EKS cluster") + } + } + + return nil +} + func (s *Service) reconcileLogging(logging *eks.Logging) error { input := eks.UpdateClusterConfigInput{Name: aws.String(s.scope.KubernetesClusterName())}