diff --git a/lib/integrations/awsoidc/eks_list_clusters.go b/lib/integrations/awsoidc/eks_list_clusters.go index 8fab7908c502c..4079f98034b1b 100644 --- a/lib/integrations/awsoidc/eks_list_clusters.go +++ b/lib/integrations/awsoidc/eks_list_clusters.go @@ -72,6 +72,16 @@ type EKSCluster struct { // Status is a current status of an EKS cluster in AWS. Status string + + // AuthenticationMode contains the authentication mode of the cluster. + // Expected values are: API, API_AND_CONFIG_MAP, CONFIG_MAP. + // Only API and API_AND_CONFIG_MAP are supported when installing the Teleport Helm chart. + // https://aws.amazon.com/blogs/containers/a-deep-dive-into-simplified-amazon-eks-access-management-controls/ + AuthenticationMode string + + // EndpointPublicAddress indicates whether the Cluster's VPC Config has its endpoint as a public address. + // For Teleport Cloud, this is required to access the cluster and proceed with the installation. + EndpointPublicAddress bool } // ListEKSClustersResponse contains a page of AWS EKS Clusters. @@ -152,19 +162,23 @@ func ListEKSClusters(ctx context.Context, clt ListEKSClustersClient, req ListEKS return nil } - extraLabels, err := getExtraEKSLabels(eksClusterInfo.Cluster) + cluster := eksClusterInfo.Cluster + + extraLabels, err := getExtraEKSLabels(cluster) if err != nil { ret.ClusterFetchingErrors[clusterName] = err return nil } ret.Clusters = append(ret.Clusters, EKSCluster{ - Name: aws.ToString(eksClusterInfo.Cluster.Name), - Region: req.Region, - Arn: aws.ToString(eksClusterInfo.Cluster.Arn), - Labels: eksClusterInfo.Cluster.Tags, - JoinLabels: extraLabels, - Status: strings.ToLower(string(eksClusterInfo.Cluster.Status)), + Name: aws.ToString(cluster.Name), + Region: req.Region, + Arn: aws.ToString(cluster.Arn), + Labels: cluster.Tags, + JoinLabels: extraLabels, + Status: strings.ToLower(string(cluster.Status)), + AuthenticationMode: string(cluster.AccessConfig.AuthenticationMode), + EndpointPublicAddress: cluster.ResourcesVpcConfig.EndpointPublicAccess, }) return nil }) diff --git a/lib/integrations/awsoidc/eks_list_clusters_test.go b/lib/integrations/awsoidc/eks_list_clusters_test.go index af60de767bd38..64298bdc6fc4d 100644 --- a/lib/integrations/awsoidc/eks_list_clusters_test.go +++ b/lib/integrations/awsoidc/eks_list_clusters_test.go @@ -101,6 +101,12 @@ func TestListEKSClusters(t *testing.T) { Arn: aws.String(fmt.Sprintf("%s_%d", baseArn, c)), Tags: map[string]string{"label": "value"}, Status: "active", + AccessConfig: &eksTypes.AccessConfigResponse{ + AuthenticationMode: eksTypes.AuthenticationModeApi, + }, + ResourcesVpcConfig: &eksTypes.VpcConfigResponse{ + EndpointPublicAccess: true, + }, }) } @@ -153,6 +159,12 @@ func TestListEKSClusters(t *testing.T) { Name: aws.String("EKS"), Status: "active", Tags: map[string]string{"label": "value"}, + AccessConfig: &eksTypes.AccessConfigResponse{ + AuthenticationMode: eksTypes.AuthenticationModeApi, + }, + ResourcesVpcConfig: &eksTypes.VpcConfigResponse{ + EndpointPublicAccess: true, + }, }, }, expectedClusters: []EKSCluster{ @@ -166,7 +178,9 @@ func TestListEKSClusters(t *testing.T) { "region": "us-east-1", "teleport.dev/cloud": "AWS", }, - Status: "active", + Status: "active", + AuthenticationMode: "API", + EndpointPublicAddress: true, }, }, expectedFetchingErrors: map[string]error{}, @@ -179,12 +193,24 @@ func TestListEKSClusters(t *testing.T) { Name: aws.String("EKS"), Status: "active", Tags: map[string]string{"label": "value"}, + AccessConfig: &eksTypes.AccessConfigResponse{ + AuthenticationMode: eksTypes.AuthenticationModeApi, + }, + ResourcesVpcConfig: &eksTypes.VpcConfigResponse{ + EndpointPublicAccess: true, + }, }, { Arn: aws.String(baseArn + "2"), Name: aws.String("EKS2"), Status: "active", Tags: map[string]string{"label2": "value2"}, + AccessConfig: &eksTypes.AccessConfigResponse{ + AuthenticationMode: eksTypes.AuthenticationModeApi, + }, + ResourcesVpcConfig: &eksTypes.VpcConfigResponse{ + EndpointPublicAccess: true, + }, }, }, expectedClusters: []EKSCluster{ @@ -198,7 +224,9 @@ func TestListEKSClusters(t *testing.T) { "region": "us-east-1", "teleport.dev/cloud": "AWS", }, - Status: "active", + Status: "active", + AuthenticationMode: "API", + EndpointPublicAddress: true, }, { Name: "EKS2", @@ -210,25 +238,51 @@ func TestListEKSClusters(t *testing.T) { "region": "us-east-1", "teleport.dev/cloud": "AWS", }, - Status: "active", + Status: "active", + AuthenticationMode: "API", + EndpointPublicAddress: true, }, }, expectedFetchingErrors: map[string]error{}, }, { - name: "two clusters, one success, one error", + name: "three clusters, one success, two error", inputEKSClusters: []eksTypes.Cluster{ { Arn: aws.String(baseArn), Name: aws.String("EKS"), Status: "active", Tags: map[string]string{"label": "value"}, + AccessConfig: &eksTypes.AccessConfigResponse{ + AuthenticationMode: eksTypes.AuthenticationModeApi, + }, + ResourcesVpcConfig: &eksTypes.VpcConfigResponse{ + EndpointPublicAccess: true, + }, + }, + { + Arn: aws.String(baseArn), + Name: aws.String("erroredCluster"), + Status: "active", + Tags: map[string]string{"label2": "value2"}, + AccessConfig: &eksTypes.AccessConfigResponse{ + AuthenticationMode: eksTypes.AuthenticationModeApi, + }, + ResourcesVpcConfig: &eksTypes.VpcConfigResponse{ + EndpointPublicAccess: true, + }, }, { Arn: aws.String(baseArn), Name: aws.String("erroredCluster"), Status: "active", Tags: map[string]string{"label2": "value2"}, + AccessConfig: &eksTypes.AccessConfigResponse{ + AuthenticationMode: eksTypes.AuthenticationModeConfigMap, + }, + ResourcesVpcConfig: &eksTypes.VpcConfigResponse{ + EndpointPublicAccess: true, + }, }, }, expectedClusters: []EKSCluster{ @@ -242,7 +296,9 @@ func TestListEKSClusters(t *testing.T) { "region": "us-east-1", "teleport.dev/cloud": "AWS", }, - Status: "active", + Status: "active", + AuthenticationMode: "API", + EndpointPublicAddress: true, }, }, expectedFetchingErrors: map[string]error{"erroredCluster": errors.New("erroredCluster")}, diff --git a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EksClustersList.tsx b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EksClustersList.tsx index aa62b95b9871b..24b4702317545 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EksClustersList.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EksClustersList.tsx @@ -29,6 +29,8 @@ import { labelMatcher, } from 'teleport/Discover/Shared'; +import cfg from 'teleport/config'; + import { CheckedEksCluster } from './EnrollEksCluster'; type Props = { @@ -121,26 +123,54 @@ function getStatus(item: CheckedEksCluster) { } } -function disabledStates(item: CheckedEksCluster, autoDiscovery: boolean) { - const disabled = - getStatus(item) !== ItemStatus.Success || - item.kubeServerExists || - autoDiscovery; +function disabledStates( + item: CheckedEksCluster, + autoDiscovery: boolean +): { disabled: boolean; disabledText: string } { + if (autoDiscovery) { + return { + disabled: true, + disabledText: 'All eligible EKS clusters will be enrolled automatically', + }; + } - let disabledText = `This EKS cluster is already enrolled and is a part of this cluster`; - switch (item.status) { - case 'failed': - case 'pending': - case 'creating': - case 'updating': - disabledText = 'Not available, try refreshing the list'; - break; - case 'deleting': - disabledText = 'Not available'; + if (item.kubeServerExists) { + return { + disabled: true, + disabledText: + 'This EKS cluster is already enrolled and is a part of this cluster', + }; } - if (autoDiscovery) { - disabledText = 'All eligible EKS clusters will be enrolled automatically'; + + if (cfg.isCloud && !item.endpointPublicAddress) { + return { + disabled: true, + disabledText: + 'Please enable endpoint public access in your EKS cluster and try again.', + }; + } + + if ( + item.authenticationMode !== 'API' && + item.authenticationMode !== 'API_AND_CONFIG_MAP' + ) { + return { + disabled: true, + disabledText: + 'Only API and API_AND_CONFIG_MAP authentication modes are supported.', + }; + } + + if (['pending', 'creating', 'updating'].includes(item.status)) { + return { + disabled: true, + disabledText: 'Not available, try refreshing the list', + }; + } + + if (['failed', 'deleting'].includes(item.status)) { + return { disabled: true, disabledText: 'Not available' }; } - return { disabled, disabledText }; + return { disabled: false, disabledText: '' }; } diff --git a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEKSCluster.test.tsx b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEKSCluster.test.tsx index 19a1d47f9df6c..462c8d691cff5 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEKSCluster.test.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEKSCluster.test.tsx @@ -214,6 +214,8 @@ const mockEKSClusters: AwsEksCluster[] = [ status: 'active', labels: [], joinLabels: [], + authenticationMode: 'API', + endpointPublicAddress: true, }, ]; diff --git a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.story.tsx b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.story.tsx index 684b2facd8231..0fc0eb811025e 100644 --- a/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.story.tsx +++ b/web/packages/teleport/src/Discover/Kubernetes/EnrollEKSCluster/EnrollEksCluster.story.tsx @@ -303,6 +303,8 @@ const eksClusters: AwsEksCluster[] = [ { name: 'region', value: 'us-east-1' }, { name: 'account-id', value: '1234567789012' }, ], + authenticationMode: 'API', + endpointPublicAddress: true, }, { name: 'EKS2', @@ -315,6 +317,8 @@ const eksClusters: AwsEksCluster[] = [ { name: 'region', value: 'us-east1' }, { name: 'account-id', value: '1234567789012' }, ], + authenticationMode: 'API', + endpointPublicAddress: true, }, { name: 'EKS3', @@ -327,5 +331,35 @@ const eksClusters: AwsEksCluster[] = [ { name: 'region', value: 'us-east-1' }, { name: 'account-id', value: '1234567789012' }, ], + authenticationMode: 'API', + endpointPublicAddress: true, + }, + { + name: 'EKS4', + region: 'us-east-1', + accountId: '123456789012', + status: 'active', + labels: [{ name: 'env', value: 'prod' }], + joinLabels: [ + { name: 'teleport.dev/cloud', value: 'AWS' }, + { name: 'region', value: 'us-east-1' }, + { name: 'account-id', value: '1234567789012' }, + ], + authenticationMode: 'CONFIG_MAP', + endpointPublicAddress: true, + }, + { + name: 'EKS5', + region: 'us-east-1', + accountId: '123456789012', + status: 'active', + labels: [{ name: 'env', value: 'prod' }], + joinLabels: [ + { name: 'teleport.dev/cloud', value: 'AWS' }, + { name: 'region', value: 'us-east-1' }, + { name: 'account-id', value: '1234567789012' }, + ], + authenticationMode: 'API_AND_CONFIG_MAP', + endpointPublicAddress: false, }, ]; diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts index 0d88ba8299843..214c98624d140 100644 --- a/web/packages/teleport/src/services/integrations/types.ts +++ b/web/packages/teleport/src/services/integrations/types.ts @@ -452,6 +452,19 @@ export type AwsEksCluster = { * joinLabels contains labels that should be injected into teleport kube agent, if EKS cluster is being enrolled. */ joinLabels: Label[]; + + /** + * AuthenticationMode is the cluster's configured authentication mode. + * You can read more about the Authentication Modes here: https://aws.amazon.com/blogs/containers/a-deep-dive-into-simplified-amazon-eks-access-management-controls/ + */ + authenticationMode: 'API' | 'API_AND_CONFIG_MAP' | 'CONFIG_MAP'; + + /** + * EndpointPublicAddress indicates whether this cluster is publicly accessible. + * This is a requirement for Teleport Cloud tenants because the control plane must be able to access the EKS Cluster + * in order to deploy the helm chart. + */ + endpointPublicAddress: boolean; }; export type EnrollEksClustersRequest = {