Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 55 additions & 50 deletions tool/tsh/common/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -1210,22 +1210,13 @@ func (c *kubeLoginCommand) run(cf *CLIConf) error {
case !c.all && c.getSelectors().IsEmpty():
return trace.BadParameter("kube-cluster name is required. Check 'tsh kube ls' for a list of available clusters.")
}
// If --all, --query, or --labels and --set-context-name are set, ensure
// that the template is valid and can produce distinct context names for
// each cluster before proceeding.
if c.all || c.labels != "" || c.predicateExpression != "" {
err := kubeconfig.CheckContextOverrideTemplate(c.overrideContextName)
if err != nil {
return trace.Wrap(err)
}
}

// NOTE: in case relogin-retry logic is used, we want to avoid having
// cf.KubernetesCluster set because kube cluster selection by prefix name is
// not supported in that flow
// cf.KubernetesCluster set because kube cluster selection by discovered
// name is not supported in that flow
// (it's equivalent to tsh login --kube-cluster=<name>).
// We will set that flag later, after listing the kube clusters and choosing
// one by prefix/labels/query (if a cluster name/prefix was given).
// one by name/labels/query (if a cluster name was given).
cf.Labels = c.labels
cf.PredicateExpression = c.predicateExpression

Expand All @@ -1238,31 +1229,44 @@ func (c *kubeLoginCommand) run(cf *CLIConf) error {
cf.disableAccessRequest = c.disableAccessRequest
cf.RequestReason = c.requestReason
cf.ListAll = c.all

// If --all, --query, or --labels and --set-context-name are set, multiple
// kube clusters may be logged into - in that case, ensure that the template
// is valid and can produce distinct context names for each cluster before
// proceeding.
if !shouldLoginToOneKubeCluster(cf) {
err := kubeconfig.CheckContextOverrideTemplate(c.overrideContextName)
if err != nil {
return trace.Wrap(err)
}
}

tc, err := makeClient(cf)
if err != nil {
return trace.Wrap(err)
}

var kubeStatus *kubernetesStatus
err = retryWithAccessRequest(cf, tc, func() error {
return client.RetryWithRelogin(cf.Context, tc, func() error {
// Check that this kube cluster exists.
err := client.RetryWithRelogin(cf.Context, tc, func() error {
var err error
kubeStatus, err = fetchKubeStatus(cf.Context, tc)
if err != nil {
return trace.Wrap(err)
}
err = c.checkClusterSelection(cf, tc, kubeStatus.kubeClusters)
if err != nil {
if trace.IsNotFound(err) {
// rewrap not found error as access denied, so we can retry
// fetching clusters with an access request.
return trace.AccessDenied(err.Error())
}
return trace.Wrap(err)
}
return nil
return trace.Wrap(err)
})
if err != nil {
return trace.Wrap(err)
}
// Check the kube cluster selection.
err = c.checkClusterSelection(cf, tc, kubeStatus.kubeClusters)
if err != nil {
if trace.IsNotFound(err) {
// rewrap not found errors as access denied, so we can retry
// fetching clusters with an access request.
return trace.AccessDenied(err.Error())
}
return trace.Wrap(err)
}
return nil
}, c.accessRequestForKubeCluster, c.selectorsOrWildcard())
if err != nil {
return trace.Wrap(err)
Expand Down Expand Up @@ -1300,7 +1304,7 @@ func (c *kubeLoginCommand) selectorsOrWildcard() string {

// checkClusterSelection checks the kube clusters selected by user input.
func (c *kubeLoginCommand) checkClusterSelection(cf *CLIConf, tc *client.TeleportClient, clusters types.KubeClusters) error {
clusters = matchClustersByName(c.kubeCluster, clusters)
clusters = matchClustersByNameOrDiscoveredName(c.kubeCluster, clusters)
err := checkClusterSelection(cf, clusters, c.kubeCluster)
if err != nil {
return trace.Wrap(err)
Expand All @@ -1320,7 +1324,7 @@ func (c *kubeLoginCommand) checkClusterSelection(cf *CLIConf, tc *client.Telepor
func checkClusterSelection(cf *CLIConf, clusters types.KubeClusters, name string) error {
switch {
// If the user is trying to login to a specific cluster, check that it
// exists and that a cluster matched the name/prefix unambiguously.
// exists and that a cluster matched the name unambiguously.
case name != "" && len(clusters) == 1:
return nil
// allow multiple selection without a name.
Expand Down Expand Up @@ -1351,42 +1355,27 @@ func (c *kubeLoginCommand) getSelectors() resourceSelectors {
}
}

func matchClustersByName(nameOrPrefix string, clusters types.KubeClusters) types.KubeClusters {
if nameOrPrefix == "" {
func matchClustersByNameOrDiscoveredName(name string, clusters types.KubeClusters) types.KubeClusters {
if name == "" {
return clusters
}

// look for an exact full name match.
for _, kc := range clusters {
if kc.GetName() == nameOrPrefix {
if kc.GetName() == name {
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)
}
}
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
return out
}

func (c *kubeLoginCommand) printUserMessage(cf *CLIConf, tc *client.TeleportClient, names []string) {
Expand Down Expand Up @@ -1545,7 +1534,7 @@ func buildKubeConfigUpdate(cf *CLIConf, kubeStatus *kubernetesStatus, overrideCo
return nil, trace.NotFound("Kubernetes cluster %q is not registered in this Teleport cluster; you can list registered Kubernetes clusters using 'tsh kube ls'.", cf.KubernetesCluster)
}
// If ListAll or labels/query is not enabled, update only cf.KubernetesCluster cluster.
if !cf.ListAll && cf.Labels == "" && cf.PredicateExpression == "" {
if shouldLoginToOneKubeCluster(cf) {
clusterNames = []string{cf.KubernetesCluster}
}
}
Expand Down Expand Up @@ -1649,11 +1638,15 @@ func (c *kubeLoginCommand) accessRequestForKubeCluster(ctx context.Context, cf *
}
defer clt.Close()

predicate := ""
if shouldLoginToOneKubeCluster(cf) {
predicate = makeDiscoveredNameOrNamePredicate(c.kubeCluster)
}
kubes, err := apiclient.GetAllResources[types.KubeCluster](ctx, clt.AuthClient, &proto.ListResourcesRequest{
Namespace: apidefaults.Namespace,
ResourceType: types.KindKubernetesCluster,
UseSearchAsRoles: true,
PredicateExpression: tc.PredicateExpression,
PredicateExpression: makePredicateConjunction(predicate, tc.PredicateExpression),
Labels: tc.Labels,
})
if err != nil {
Expand Down Expand Up @@ -1696,6 +1689,18 @@ func (c *kubeLoginCommand) accessRequestForKubeCluster(ctx context.Context, cf *
return req, nil
}

// shouldLoginToOneKubeCluster returns whether a single kube cluster should be
// logged into (meaning update the kube config for one kube cluster).
// NOTE: it does not check `cf.KubernetesCluster != ""` because that flag is not
// populated from the kubeLoginCommand flag until later
// (due to the relogin-retry logic interaction with "discovered name" matching).
// `tsh kube login` requires a kube cluster name arg when none of --all,
// --labels, or --query are given, so when all of those flags are not set, it
// implies that a kube cluster name was given.
func shouldLoginToOneKubeCluster(cf *CLIConf) bool {
return !cf.ListAll && cf.Labels == "" && cf.PredicateExpression == ""
}

// formatAmbiguousKubeCluster is a helper func that formats an ambiguous kube
// cluster error message.
func formatAmbiguousKubeCluster(cf *CLIConf, selectors resourceSelectors, kubeClusters types.KubeClusters) string {
Expand Down
4 changes: 2 additions & 2 deletions tool/tsh/common/kube_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,11 +540,11 @@ func combineMatchedClusters(matchMap map[string]types.KubeClusters) types.KubeCl
}

// matchClustersByNames maps each name to the clusters it matches by exact name
// or by prefix.
// or by discovered name.
func matchClustersByNames(clusters types.KubeClusters, names ...string) map[string]types.KubeClusters {
matchesForNames := make(map[string]types.KubeClusters)
for _, name := range names {
matchesForNames[name] = matchClustersByName(name, clusters)
matchesForNames[name] = matchClustersByNameOrDiscoveredName(name, clusters)
}
return matchesForNames
}
Expand Down
Loading