diff --git a/tool/tsh/kube.go b/tool/tsh/kube.go index ac0d43e60e49e..47c4a6924f9f3 100644 --- a/tool/tsh/kube.go +++ b/tool/tsh/kube.go @@ -1300,10 +1300,7 @@ func checkClusterSelection(cf *CLIConf, clusters types.KubeClusters, name string query: cf.PredicateExpression, } if len(clusters) == 0 { - if selectors.IsEmpty() { - return trace.NotFound("no kubernetes clusters found, check 'tsh kube ls' for a list of known clusters") - } - return trace.NotFound("%v not found, check 'tsh kube ls' for a list of known clusters", selectors.String()) + return trace.NotFound(formatKubeNotFound(cf.SiteName, selectors)) } errMsg := formatAmbiguousKubeCluster(cf, selectors, clusters) return trace.BadParameter(errMsg) @@ -1322,11 +1319,22 @@ func matchClustersByName(nameOrPrefix string, clusters types.KubeClusters) types if nameOrPrefix == "" { return clusters } - var prefixMatches types.KubeClusters + + // look for an exact full name match. for _, kc := range clusters { if kc.GetName() == nameOrPrefix { return types.KubeClusters{kc} } + } + + // or look for exact "discovered name" matches. + if clusters, ok := findKubeClustersByDiscoveredName(clusters, nameOrPrefix); ok { + return clusters + } + + // or just filter by prefix. + var prefixMatches types.KubeClusters + for _, kc := range clusters { if strings.HasPrefix(kc.GetName(), nameOrPrefix) { prefixMatches = append(prefixMatches, kc) } @@ -1334,6 +1342,17 @@ func matchClustersByName(nameOrPrefix string, clusters types.KubeClusters) types return prefixMatches } +func findKubeClustersByDiscoveredName(clusters types.KubeClusters, name string) (types.KubeClusters, bool) { + var out types.KubeClusters + for _, kc := range clusters { + discoveredName, ok := kc.GetLabel(types.DiscoveredNameLabel) + if ok && discoveredName == name { + out = append(out, kc) + } + } + return out, len(out) > 0 +} + func (c *kubeLoginCommand) printUserMessage(cf *CLIConf, tc *client.TeleportClient, names []string) { if tc.Profile().RequireKubeLocalProxy() { c.printLocalProxyUserMessage(cf, names) @@ -1599,6 +1618,16 @@ func formatAmbiguousKubeCluster(cf *CLIConf, selectors resourceSelectors, kubeCl return formatAmbiguityErrTemplate(cf, selectors, listCommand, table, fullNameExample) } +func formatKubeNotFound(clusterFlag string, selectors resourceSelectors) string { + listCmd := formatKubeListCommand(clusterFlag) + if selectors.IsEmpty() { + return fmt.Sprintf("no kubernetes clusters found, check '%v' for a list of known clusters", + listCmd) + } + return fmt.Sprintf("%v not found, check '%v' for a list of known clusters", + selectors, listCmd) +} + func formatKubeListCommand(clusterFlag string) string { if clusterFlag == "" { return "tsh kube ls" diff --git a/tool/tsh/kube_proxy_test.go b/tool/tsh/kube_proxy_test.go index 420aae06a6814..423013f0f7df2 100644 --- a/tool/tsh/kube_proxy_test.go +++ b/tool/tsh/kube_proxy_test.go @@ -25,8 +25,10 @@ import ( "path" "strings" "testing" + "time" "github.com/google/uuid" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -34,9 +36,11 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "github.com/gravitational/teleport/api/types" + apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/api/utils/keypaths" "github.com/gravitational/teleport/lib/kube/kubeconfig" "github.com/gravitational/teleport/lib/service/servicecfg" + "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/srv/alpnproxy/common" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/tool/teleport/testenv" @@ -94,7 +98,8 @@ func TestProxyKubeComplexSelectors(t *testing.T) { testenv.WithResyncInterval(t, 0) kubeFoo := "foo" kubeFooBar := "foo-bar" - kubeBaz := "baz" + kubeBaz := "baz-qux" + kubeBazEKS := "baz-eks-us-west-1-123456789012" kubeFooLeaf := "foo" ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -106,6 +111,9 @@ func TestProxyKubeComplexSelectors(t *testing.T) { cfg.Kube.ListenAddr = utils.MustParseAddr(localListenerAddr()) cfg.Kube.KubeconfigPath = newKubeConfigFile(t, kubeFoo, kubeFooBar, kubeBaz) cfg.Kube.StaticLabels = map[string]string{"env": "root"} + cfg.Kube.ResourceMatchers = []services.ResourceMatcher{{ + Labels: map[string]apiutils.Strings{"*": {"*"}}, + }} }), withLeafCluster(), withLeafConfigFunc( @@ -126,6 +134,34 @@ func TestProxyKubeComplexSelectors(t *testing.T) { return len(rootClusters) == 3 && len(leafClusters) == 1 }), ) + // setup a fake "discovered" kube cluster by adding a discovered name label + // to a dynamic kube cluster. + kc, err := types.NewKubernetesClusterV3( + types.Metadata{ + Name: kubeBazEKS, + Labels: map[string]string{ + types.DiscoveredNameLabel: "baz", + types.OriginLabel: types.OriginDynamic, + }, + }, + types.KubernetesClusterSpecV3{ + Kubeconfig: newKubeConfig(t, kubeBazEKS), + }, + ) + require.NoError(t, err) + err = s.root.GetAuthServer().CreateKubernetesCluster(ctx, kc) + require.NoError(t, err) + require.EventuallyWithT(t, func(c *assert.CollectT) { + servers, err := s.root.GetAuthServer().GetKubernetesServers(ctx) + assert.NoError(c, err) + for _, ks := range servers { + if ks.GetName() == kubeBazEKS { + return + } + } + assert.Fail(c, "kube server not found") + }, time.Second*10, time.Millisecond*500, "failed to find dynamically created kube cluster %v", kubeBazEKS) + rootClusterName := s.root.Config.Auth.ClusterName.GetClusterName() leafClusterName := s.leaf.Config.Auth.ClusterName.GetClusterName() @@ -146,6 +182,17 @@ func TestProxyKubeComplexSelectors(t *testing.T) { }, args: []string{kubeFoo, "--insecure"}, }, + { + desc: "with discovered name", + makeValidateCmdFn: func(t *testing.T) func(*exec.Cmd) error { + return func(cmd *exec.Cmd) error { + config := kubeConfigFromCmdEnv(t, cmd) + checkKubeLocalProxyConfig(t, s, config, rootClusterName, kubeBazEKS) + return nil + } + }, + args: []string{"baz", "--insecure"}, + }, { desc: "with prefix name", makeValidateCmdFn: func(t *testing.T) func(*exec.Cmd) error { diff --git a/tool/tsh/kube_test.go b/tool/tsh/kube_test.go index f334c7fa3dc3f..feef1afa236db 100644 --- a/tool/tsh/kube_test.go +++ b/tool/tsh/kube_test.go @@ -461,6 +461,25 @@ func newKubeConfigFile(t *testing.T, clusterNames ...string) string { return kubeConfigLocation } +func newKubeConfig(t *testing.T, name string) []byte { + kubeConf := clientcmdapi.NewConfig() + + kubeConf.Clusters[name] = &clientcmdapi.Cluster{ + Server: newKubeSelfSubjectServer(t), + InsecureSkipTLSVerify: true, + } + kubeConf.AuthInfos[name] = &clientcmdapi.AuthInfo{} + + kubeConf.Contexts[name] = &clientcmdapi.Context{ + Cluster: name, + AuthInfo: name, + } + + buf, err := clientcmd.Write(*kubeConf) + require.NoError(t, err) + return buf +} + func newKubeSelfSubjectServer(t *testing.T) string { srv, err := kubeserver.NewKubeAPIMock() require.NoError(t, err)