diff --git a/lib/usertasks/descriptions.go b/lib/usertasks/descriptions.go
index eb1655fee5ea7..3068e1d02b023 100644
--- a/lib/usertasks/descriptions.go
+++ b/lib/usertasks/descriptions.go
@@ -26,10 +26,7 @@ import (
//go:embed descriptions/*.md
var descriptionsFS embed.FS
-// DescriptionForDiscoverEC2Issue returns the description of the issue and fixing steps.
-// The returned string contains a markdown document.
-// If issue type is not recognized or doesn't have a specific description, them an empty string is returned.
-func DescriptionForDiscoverEC2Issue(issueType string) string {
+func loadIssueDescription(issueType string) string {
filename := fmt.Sprintf("descriptions/%s.md", issueType)
bs, err := descriptionsFS.ReadFile(filename)
if err != nil {
@@ -37,3 +34,17 @@ func DescriptionForDiscoverEC2Issue(issueType string) string {
}
return string(bs)
}
+
+// DescriptionForDiscoverEC2Issue returns the description of the issue and fixing steps.
+// The returned string contains a markdown document.
+// If issue type is not recognized or doesn't have a specific description, them an empty string is returned.
+func DescriptionForDiscoverEC2Issue(issueType string) string {
+ return loadIssueDescription(issueType)
+}
+
+// DescriptionForDiscoverEKSIssue returns the description of the issue and fixing steps.
+// The returned string contains a markdown document.
+// If issue type is not recognized or doesn't have a specific description, them an empty string is returned.
+func DescriptionForDiscoverEKSIssue(issueType string) string {
+ return loadIssueDescription(issueType)
+}
diff --git a/lib/usertasks/descriptions/eks-agent-not-connecting.md b/lib/usertasks/descriptions/eks-agent-not-connecting.md
new file mode 100644
index 0000000000000..60d2a0b2c02a9
--- /dev/null
+++ b/lib/usertasks/descriptions/eks-agent-not-connecting.md
@@ -0,0 +1,8 @@
+The process of automatically enrolling EKS Clusters into Teleport, starts by installing the [`teleport-kube-agent`](https://goteleport.com/docs/reference/helm-reference/teleport-kube-agent/) to the cluster.
+
+If the installation is successful, the EKS Cluster will appear in your Resources list.
+
+However, the following EKS Clusters did not automatically enrolled.
+This usually happens when the installation is taking too long or there was an error preventing the HELM chart installation.
+
+Open the Teleport Agent to get more information.
\ No newline at end of file
diff --git a/lib/usertasks/descriptions/eks-authentication-mode-unsupported.md b/lib/usertasks/descriptions/eks-authentication-mode-unsupported.md
new file mode 100644
index 0000000000000..c6d3f55e569c8
--- /dev/null
+++ b/lib/usertasks/descriptions/eks-authentication-mode-unsupported.md
@@ -0,0 +1,3 @@
+Teleport uses the Amazon EKS API to install the Teleport Kubernetes Agent.
+
+Please enable API (or API and Config Map) authentication mode in the following EKS Clusters so that they can be automatically enrolled.
\ No newline at end of file
diff --git a/lib/usertasks/descriptions/eks-cluster-unreachable.md b/lib/usertasks/descriptions/eks-cluster-unreachable.md
new file mode 100644
index 0000000000000..f1cf31beed18f
--- /dev/null
+++ b/lib/usertasks/descriptions/eks-cluster-unreachable.md
@@ -0,0 +1,5 @@
+The EKS Cluster must be accessible from the Teleport Auth Service in order for Teleport to deploy the Teleport Kubernetes Agent.
+
+The following EKS Clusters couldn't be accessed.
+
+Ensure their network endpoint access configuration allows access from Teleport.
\ No newline at end of file
diff --git a/lib/usertasks/descriptions/eks-missing-endpoint-public-access.md b/lib/usertasks/descriptions/eks-missing-endpoint-public-access.md
new file mode 100644
index 0000000000000..d1e2713c9b75c
--- /dev/null
+++ b/lib/usertasks/descriptions/eks-missing-endpoint-public-access.md
@@ -0,0 +1,3 @@
+The EKS Cluster must be publicly accessible in order for Teleport to deploy the Teleport Kubernetes Agent.
+
+You can enable the public endpoint by accessing the Manage Endpoint Access.
\ No newline at end of file
diff --git a/lib/usertasks/descriptions/eks-status-not-active.md b/lib/usertasks/descriptions/eks-status-not-active.md
new file mode 100644
index 0000000000000..831f22ba99f15
--- /dev/null
+++ b/lib/usertasks/descriptions/eks-status-not-active.md
@@ -0,0 +1,3 @@
+Only EKS Clusters whose status is active can be automatically enrolled into teleport.
+
+The following are not active.
\ No newline at end of file
diff --git a/lib/usertasks/descriptions_test.go b/lib/usertasks/descriptions_test.go
index 30a358ae1ea9d..6d0d2f4cd371f 100644
--- a/lib/usertasks/descriptions_test.go
+++ b/lib/usertasks/descriptions_test.go
@@ -30,4 +30,7 @@ func TestAllDescriptions(t *testing.T) {
for _, issueType := range usertasksapi.DiscoverEC2IssueTypes {
require.NotEmpty(t, DescriptionForDiscoverEC2Issue(issueType), "issue type %q is missing descriptions/%s.md file", issueType, issueType)
}
+ for _, issueType := range usertasksapi.DiscoverEKSIssueTypes {
+ require.NotEmpty(t, DescriptionForDiscoverEKSIssue(issueType), "issue type %q is missing descriptions/%s.md file", issueType, issueType)
+ }
}
diff --git a/lib/usertasks/urls.go b/lib/usertasks/urls.go
new file mode 100644
index 0000000000000..1f95960af0cb2
--- /dev/null
+++ b/lib/usertasks/urls.go
@@ -0,0 +1,174 @@
+/*
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package usertasks
+
+import (
+ "net/url"
+ "path"
+
+ usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1"
+ usertasksapi "github.com/gravitational/teleport/api/types/usertasks"
+)
+
+// UserTaskDiscoverEKSWithURLs contains the clusters that failed to auto-enroll into the cluster.
+type UserTaskDiscoverEKSWithURLs struct {
+ *usertasksv1.DiscoverEKS
+ // Clusters maps a cluster name to the result of enrolling that cluster into teleport.
+ Clusters map[string]*DiscoverEKSClusterWithURLs `json:"clusters,omitempty"`
+}
+
+// DiscoverEKSClusterWithURLs contains the result of enrolling an AWS EKS Cluster.
+type DiscoverEKSClusterWithURLs struct {
+ *usertasksv1.DiscoverEKSCluster
+
+ // ResourceURL is the Amazon Web Console URL to access this EKS Cluster.
+ // Always present.
+ // Format: https://console.aws.amazon.com/eks/home?region=#/clusters/
+ ResourceURL string `json:"resourceUrl,omitempty"`
+
+ // OpenTeleportAgentURL is the URL to open the Teleport Agent StatefulSet in Amazon EKS Web Console.
+ // Present when issue is of type eks-agent-not-connecting.
+ // Format: https://console.aws.amazon.com/eks/home?region=#/clusters//statefulsets/teleport-kube-agent?namespace=teleport-agent
+ OpenTeleportAgentURL string `json:"openTeleportAgentUrl,omitempty"`
+
+ // ManageAccessURL is the URL to open the EKS in Amazon Web Console, in the Manage Access page.
+ // Present when issue is of type eks-authentication-mode-unsupported.
+ // Format: https://console.aws.amazon.com/eks/home?region=#/clusters//manage-access
+ ManageAccessURL string `json:"manageAccessUrl,omitempty"`
+
+ // ManageEndpointAccessURL is the URL to open the EKS in Amazon Web Console, in the Manage Endpoint Access page.
+ // Present when issue is of type eks-cluster-unreachable and eks-missing-endpoint-public-access.
+ // Format: https://console.aws.amazon.com/eks/home?region=#/clusters//manage-endpoint-access
+ ManageEndpointAccessURL string `json:"manageEndpointAccessUrl,omitempty"`
+
+ // ManageClusterURL is the URL to open the EKS Cluster in Amazon Web Console.
+ // Present when issue is of type eks-status-not-active.
+ // Format: https://console.aws.amazon.com/eks/home?region=#/clusters/
+ ManageClusterURL string `json:"manageClusterUrl,omitempty"`
+}
+
+func withEKSClusterIssueURL(metadata *usertasksv1.UserTask, cluster *usertasksv1.DiscoverEKSCluster) *DiscoverEKSClusterWithURLs {
+ ret := &DiscoverEKSClusterWithURLs{
+ DiscoverEKSCluster: cluster,
+ }
+ clusterBaseURL := url.URL{
+ Scheme: "https",
+ Host: "console.aws.amazon.com",
+ Path: path.Join("eks", "home"),
+ Fragment: "/clusters/" + cluster.GetName(),
+ RawQuery: url.Values{
+ "region": []string{metadata.Spec.DiscoverEks.GetRegion()},
+ }.Encode(),
+ }
+
+ ret.ResourceURL = clusterBaseURL.String()
+
+ switch metadata.Spec.IssueType {
+ case usertasksapi.AutoDiscoverEKSIssueAgentNotConnecting:
+ clusterBaseURL.Fragment = clusterBaseURL.Fragment + "/statefulsets/teleport-kube-agent?namespace=teleport-agent"
+ ret.OpenTeleportAgentURL = clusterBaseURL.String()
+
+ case usertasksapi.AutoDiscoverEKSIssueAuthenticationModeUnsupported:
+ clusterBaseURL.Fragment = clusterBaseURL.Fragment + "/manage-access"
+ ret.ManageAccessURL = clusterBaseURL.String()
+
+ case usertasksapi.AutoDiscoverEKSIssueClusterUnreachable, usertasksapi.AutoDiscoverEKSIssueMissingEndpoingPublicAccess:
+ clusterBaseURL.Fragment = clusterBaseURL.Fragment + "/manage-endpoint-access"
+ ret.ManageEndpointAccessURL = clusterBaseURL.String()
+
+ case usertasksapi.AutoDiscoverEKSIssueStatusNotActive:
+ ret.ManageClusterURL = clusterBaseURL.String()
+ }
+
+ return ret
+}
+
+// EKSClustersWithURLs takes a UserTask and enriches the cluster list with URLs.
+// Currently, the following URLs will be added:
+// - ResourceURL: a link to open the instance in Amazon Web Console.
+// The following URLs might be added depending on the issue type:
+// - OpenTeleportAgentURL: links directly to the statefulset created during the helm installation
+// - ManageAccessURL: links to the Manage Access screen in the Amazon EKS Web Console, for the current EKS Cluster.
+// - ManageEndpointAccessURL: links to the Manage Endpoint Access screen in the Amazon EKS Web Console, for the current EKS Cluster.
+// - ManageClusterURL: links to the EKS Cluster.
+func EKSClustersWithURLs(ut *usertasksv1.UserTask) *UserTaskDiscoverEKSWithURLs {
+ clusters := ut.Spec.GetDiscoverEks().GetClusters()
+ clustersWithURLs := make(map[string]*DiscoverEKSClusterWithURLs, len(clusters))
+
+ for clusterName, cluster := range clusters {
+ clustersWithURLs[clusterName] = withEKSClusterIssueURL(ut, cluster)
+ }
+
+ return &UserTaskDiscoverEKSWithURLs{
+ DiscoverEKS: ut.Spec.GetDiscoverEks(),
+ Clusters: clustersWithURLs,
+ }
+}
+
+// UserTaskDiscoverEC2WithURLs contains the instances that failed to auto-enroll into the cluster.
+type UserTaskDiscoverEC2WithURLs struct {
+ *usertasksv1.DiscoverEC2
+ // Instances maps the instance ID name to the result of enrolling that instance into teleport.
+ Instances map[string]*DiscoverEC2InstanceWithURLs `json:"clusters,omitempty"`
+}
+
+// DiscoverEC2InstanceWithURLs contains the result of enrolling an AWS EC2 Instance.
+type DiscoverEC2InstanceWithURLs struct {
+ *usertasksv1.DiscoverEC2Instance
+
+ // ResourceURL is the Amazon Web Console URL to access this EC2 Instance.
+ // Always present.
+ // Format: https://console.aws.amazon.com/ec2/home?region=#InstanceDetails:instanceId=
+ ResourceURL string `json:"resourceUrl,omitempty"`
+}
+
+func withEC2InstanceIssueURL(metadata *usertasksv1.UserTask, instance *usertasksv1.DiscoverEC2Instance) *DiscoverEC2InstanceWithURLs {
+ ret := &DiscoverEC2InstanceWithURLs{
+ DiscoverEC2Instance: instance,
+ }
+ instanceBaseURL := url.URL{
+ Scheme: "https",
+ Host: "console.aws.amazon.com",
+ Path: path.Join("ec2", "home"),
+ Fragment: "InstanceDetails:instanceId=" + instance.GetInstanceId(),
+ RawQuery: url.Values{
+ "region": []string{metadata.Spec.DiscoverEc2.GetRegion()},
+ }.Encode(),
+ }
+ ret.ResourceURL = instanceBaseURL.String()
+
+ return ret
+}
+
+// EC2InstancesWithURLs takes a UserTask and enriches the instance list with URLs.
+// Currently, the following URLs will be added:
+// - ResourceURL: a link to open the instance in Amazon Web Console.
+func EC2InstancesWithURLs(ut *usertasksv1.UserTask) *UserTaskDiscoverEC2WithURLs {
+ instances := ut.Spec.GetDiscoverEc2().GetInstances()
+ instancesWithURLs := make(map[string]*DiscoverEC2InstanceWithURLs, len(instances))
+
+ for instanceID, instance := range instances {
+ instancesWithURLs[instanceID] = withEC2InstanceIssueURL(ut, instance)
+ }
+
+ return &UserTaskDiscoverEC2WithURLs{
+ DiscoverEC2: ut.Spec.GetDiscoverEc2(),
+ Instances: instancesWithURLs,
+ }
+}
diff --git a/lib/usertasks/urls_test.go b/lib/usertasks/urls_test.go
new file mode 100644
index 0000000000000..74f8e6c065fb3
--- /dev/null
+++ b/lib/usertasks/urls_test.go
@@ -0,0 +1,151 @@
+/*
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package usertasks
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1"
+ usertasksapi "github.com/gravitational/teleport/api/types/usertasks"
+)
+
+func TestEKSURLs(t *testing.T) {
+ clusterName := "my-cluster"
+ dummyCluster := &usertasksv1.DiscoverEKSCluster{Name: clusterName}
+ baseClusterData := &usertasksv1.DiscoverEKS{
+ Region: "us-east-1",
+ Clusters: map[string]*usertasksv1.DiscoverEKSCluster{
+ clusterName: dummyCluster,
+ },
+ }
+
+ for _, tt := range []struct {
+ name string
+ issueType string
+ expectedEKSClusterWithURL *DiscoverEKSClusterWithURLs
+ expected *UserTaskDiscoverEKSWithURLs
+ }{
+ {
+ name: "url for eks agent not connecting",
+ issueType: usertasksapi.AutoDiscoverEKSIssueAgentNotConnecting,
+ expectedEKSClusterWithURL: &DiscoverEKSClusterWithURLs{
+ ResourceURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster",
+ OpenTeleportAgentURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster/statefulsets/teleport-kube-agent?namespace=teleport-agent",
+ },
+ },
+ {
+ name: "url for eks authentication mode unsupported",
+ issueType: usertasksapi.AutoDiscoverEKSIssueAuthenticationModeUnsupported,
+ expectedEKSClusterWithURL: &DiscoverEKSClusterWithURLs{
+ ResourceURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster",
+ ManageAccessURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster/manage-access",
+ },
+ },
+ {
+ name: "url for eks cluster unreachable",
+ issueType: usertasksapi.AutoDiscoverEKSIssueClusterUnreachable,
+ expectedEKSClusterWithURL: &DiscoverEKSClusterWithURLs{
+ ResourceURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster",
+ ManageEndpointAccessURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster/manage-endpoint-access",
+ },
+ },
+ {
+ name: "url for eks missing endpoint public access",
+ issueType: usertasksapi.AutoDiscoverEKSIssueMissingEndpoingPublicAccess,
+ expectedEKSClusterWithURL: &DiscoverEKSClusterWithURLs{
+ ResourceURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster",
+ ManageEndpointAccessURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster/manage-endpoint-access",
+ },
+ },
+ {
+ name: "url for eks cluster status not active",
+ issueType: usertasksapi.AutoDiscoverEKSIssueStatusNotActive,
+ expectedEKSClusterWithURL: &DiscoverEKSClusterWithURLs{
+ ResourceURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster",
+ ManageClusterURL: "https://console.aws.amazon.com/eks/home?region=us-east-1#/clusters/my-cluster",
+ },
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ clusterWithURL := tt.expectedEKSClusterWithURL
+ clusterWithURL.DiscoverEKSCluster = dummyCluster
+ expected := &UserTaskDiscoverEKSWithURLs{
+ DiscoverEKS: baseClusterData,
+ Clusters: map[string]*DiscoverEKSClusterWithURLs{
+ clusterName: clusterWithURL,
+ },
+ }
+
+ got := EKSClustersWithURLs(&usertasksv1.UserTask{
+ Spec: &usertasksv1.UserTaskSpec{
+ IssueType: tt.issueType,
+ DiscoverEks: baseClusterData,
+ },
+ })
+ require.Equal(t, expected, got)
+ })
+ }
+}
+
+func TestEC2URLs(t *testing.T) {
+ instanceID := "i-12345678"
+ dummyInstance := &usertasksv1.DiscoverEC2Instance{InstanceId: instanceID}
+ baseInstancesData := &usertasksv1.DiscoverEC2{
+ Region: "us-east-1",
+ Instances: map[string]*usertasksv1.DiscoverEC2Instance{
+ instanceID: dummyInstance,
+ },
+ }
+
+ for _, tt := range []struct {
+ name string
+ issueType string
+ expectedEC2InstanceWithURL *DiscoverEC2InstanceWithURLs
+ expected *UserTaskDiscoverEC2WithURLs
+ }{
+ {
+ name: "url for ec2 resource",
+ issueType: usertasksapi.AutoDiscoverEC2IssueSSMScriptFailure,
+ expectedEC2InstanceWithURL: &DiscoverEC2InstanceWithURLs{
+ ResourceURL: "https://console.aws.amazon.com/ec2/home?region=us-east-1#InstanceDetails:instanceId=i-12345678",
+ },
+ },
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ instanceWithURL := tt.expectedEC2InstanceWithURL
+ instanceWithURL.DiscoverEC2Instance = dummyInstance
+ expected := &UserTaskDiscoverEC2WithURLs{
+ DiscoverEC2: baseInstancesData,
+ Instances: map[string]*DiscoverEC2InstanceWithURLs{
+ instanceID: instanceWithURL,
+ },
+ }
+
+ got := EC2InstancesWithURLs(&usertasksv1.UserTask{
+ Spec: &usertasksv1.UserTaskSpec{
+ IssueType: tt.issueType,
+ DiscoverEc2: baseInstancesData,
+ },
+ })
+ require.Equal(t, expected, got)
+ })
+ }
+}
diff --git a/lib/web/ui/usertask.go b/lib/web/ui/usertask.go
index 02b174aeb1782..14fc86a8bd71f 100644
--- a/lib/web/ui/usertask.go
+++ b/lib/web/ui/usertask.go
@@ -24,6 +24,7 @@ import (
"github.com/gravitational/trace"
usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1"
+ apiusertasks "github.com/gravitational/teleport/api/types/usertasks"
"github.com/gravitational/teleport/lib/usertasks"
)
@@ -51,7 +52,9 @@ type UserTaskDetail struct {
// Description is a markdown document that explains the issue and how to fix it.
Description string `json:"description,omitempty"`
// DiscoverEC2 contains the task details for the DiscoverEC2 tasks.
- DiscoverEC2 *usertasksv1.DiscoverEC2 `json:"discoverEc2,omitempty"`
+ DiscoverEC2 *usertasks.UserTaskDiscoverEC2WithURLs `json:"discoverEc2,omitempty"`
+ // DiscoverEKS contains the task details for the DiscoverEKS tasks.
+ DiscoverEKS *usertasks.UserTaskDiscoverEKSWithURLs `json:"discoverEks,omitempty"`
}
// UpdateUserTaskStateRequest is a request to update a UserTask
@@ -92,10 +95,24 @@ func MakeUserTasks(uts []*usertasksv1.UserTask) []UserTask {
// MakeDetailedUserTask creates a UI UserTask representation containing all the details.
func MakeDetailedUserTask(ut *usertasksv1.UserTask) UserTaskDetail {
+ var description string
+ var discoverEKS *usertasks.UserTaskDiscoverEKSWithURLs
+ var discoverEC2 *usertasks.UserTaskDiscoverEC2WithURLs
+
+ switch ut.GetSpec().GetTaskType() {
+ case apiusertasks.TaskTypeDiscoverEC2:
+ description = usertasks.DescriptionForDiscoverEC2Issue(ut.GetSpec().GetIssueType())
+ discoverEC2 = usertasks.EC2InstancesWithURLs(ut)
+ case apiusertasks.TaskTypeDiscoverEKS:
+ description = usertasks.DescriptionForDiscoverEKSIssue(ut.GetSpec().GetIssueType())
+ discoverEKS = usertasks.EKSClustersWithURLs(ut)
+ }
+
return UserTaskDetail{
UserTask: MakeUserTask(ut),
- Description: usertasks.DescriptionForDiscoverEC2Issue(ut.GetSpec().GetIssueType()),
- DiscoverEC2: ut.GetSpec().GetDiscoverEc2(),
+ Description: description,
+ DiscoverEC2: discoverEC2,
+ DiscoverEKS: discoverEKS,
}
}