Files
teleport/lib/kube/utils/utils.go
T
Tiago Silva 674a3a1ec1 Removes deprecated KindKubeService objects (#22663)
This PR removes the `types.KindKubeService` objects that were
deprecated in Teleport 11.

Teleport 12 removed the double heartbeat and for Teleport 13 we can
safely drop the support of the old hearbeat based on `types.Server`
objects.

Clients older than Teleport 12 connecting to a Teleport 13 server will
fail since they cannot heartbeat using the old mechanism.
2023-03-13 10:55:39 +00:00

250 lines
8.3 KiB
Go

/*
Copyright 2020 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"context"
"encoding/hex"
"github.com/gravitational/trace"
"golang.org/x/exp/slices"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
)
// GetKubeClient returns instance of client to the kubernetes cluster
// using in-cluster configuration if available and falling back to
// configuration file under configPath otherwise
func GetKubeClient(configPath string) (client *kubernetes.Clientset, config *rest.Config, err error) {
// if path to kubeconfig was provided, init config from it
if configPath != "" {
config, err = clientcmd.BuildConfigFromFlags("", configPath)
if err != nil {
return nil, nil, trace.Wrap(err)
}
} else {
// otherwise attempt to init as if connecting from cluster
config, err = rest.InClusterConfig()
if err != nil {
return nil, nil, trace.Wrap(err)
}
}
client, err = kubernetes.NewForConfig(config)
if err != nil {
return nil, nil, trace.Wrap(err)
}
return client, config, nil
}
// Kubeconfig is a parsed kubeconfig file representation.
type Kubeconfig struct {
CurrentContext string
Contexts map[string]*rest.Config
}
// GetKubeConfig returns kubernetes configuration from configPath file or, by
// default reads in-cluster configuration. If allConfigEntries is set, the
// returned Kubeconfig will contain all contexts from the kubeconfig file;
// otherwise it only contains the current context.
//
// TODO(awly): unit test this
func GetKubeConfig(configPath string, allConfigEntries bool, clusterName string) (*Kubeconfig, error) {
switch {
case configPath != "" && clusterName == "":
loader := &clientcmd.ClientConfigLoadingRules{ExplicitPath: configPath}
cfg, err := loader.Load()
if err != nil {
return nil, trace.Wrap(err)
}
res := &Kubeconfig{
CurrentContext: cfg.CurrentContext,
Contexts: make(map[string]*rest.Config, len(cfg.Contexts)),
}
if !allConfigEntries {
// Only current-context is requested.
clientCfg, err := clientcmd.NewNonInteractiveClientConfig(*cfg, cfg.CurrentContext, &clientcmd.ConfigOverrides{}, nil).ClientConfig()
if err != nil {
return nil, trace.Wrap(err)
}
res.Contexts[cfg.CurrentContext] = clientCfg
return res, nil
}
// All contexts are requested.
for n := range cfg.Contexts {
clientCfg, err := clientcmd.NewNonInteractiveClientConfig(*cfg, n, &clientcmd.ConfigOverrides{}, nil).ClientConfig()
if err != nil {
return nil, trace.Wrap(err)
}
res.Contexts[n] = clientCfg
}
return res, nil
case configPath == "" && clusterName != "":
cfg, err := rest.InClusterConfig()
if err != nil {
if err == rest.ErrNotInCluster {
return nil, trace.NotFound("not running inside of a Kubernetes pod")
}
return nil, trace.Wrap(err)
}
return &Kubeconfig{
CurrentContext: clusterName,
Contexts: map[string]*rest.Config{clusterName: cfg},
}, nil
case configPath == "" && clusterName == "":
return nil, trace.NotFound("neither kubeconfig nor cluster name provided")
case configPath != "" && clusterName != "":
return nil, trace.BadParameter("only one of configPath or clusterName can be specified")
}
panic("unreachable")
}
// EncodeClusterName encodes cluster name for SNI matching
//
// For example:
//
// * Main cluster is main.example.com
// * Remote cluster is remote.example.com
//
// After 'tsh login' the URL of the Kubernetes endpoint of 'remote.example.com'
// when accessed 'via main.example.com' looks like this:
//
// 'k72656d6f74652e6578616d706c652e636f6d0a.main.example.com'
//
// For this to work, users have to add this address in public_addr section of kubernetes service
// to include 'main.example.com' in X509 '*.main.example.com' domain name
//
// where part '72656d6f74652e6578616d706c652e636f6d0a' is a hex encoded remote.example.com
//
// It is hex encoded to allow wildcard matching to work. In DNS wildcard match
// include only one '.'
func EncodeClusterName(clusterName string) string {
// k is to avoid first letter to be a number
return "k" + hex.EncodeToString([]byte(clusterName))
}
// KubeServicesPresence fetches a list of registered kubernetes servers.
// It's a subset of services.Presence.
type KubeServicesPresence interface {
// GetKubernetesServers returns a list of registered kubernetes servers.
GetKubernetesServers(context.Context) ([]types.KubeServer, error)
}
// KubeClusterNames returns a sorted list of unique kubernetes cluster
// names registered in p.
//
// DELETE IN 11.0.0, replaced by ListKubeClustersWithFilters
func KubeClusterNames(ctx context.Context, p KubeServicesPresence) ([]string, error) {
kss, err := p.GetKubernetesServers(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
return extractAndSortKubeClusterNames(kss), nil
}
func extractAndSortKubeClusterNames(kubeServers []types.KubeServer) []string {
kubeClusters := extractAndSortKubeClusters(kubeServers)
kubeClusterNames := make([]string, len(kubeClusters))
for i := range kubeClusters {
kubeClusterNames[i] = kubeClusters[i].GetName()
}
return kubeClusterNames
}
// KubeClusters returns a sorted list of unique kubernetes clusters
// registered in p.
//
// DELETE IN 11.0.0, replaced by ListKubeClustersWithFilters
func KubeClusters(ctx context.Context, p KubeServicesPresence) ([]types.KubeCluster, error) {
kubeServers, err := p.GetKubernetesServers(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
return extractAndSortKubeClusters(kubeServers), nil
}
// ListKubeClusterWithFilters returns a sorted list of unique kubernetes clusters
// registered in p.
func ListKubeClustersWithFilters(ctx context.Context, p client.ListResourcesClient, req proto.ListResourcesRequest) ([]types.KubeCluster, error) {
req.ResourceType = types.KindKubeServer
var kss []types.KubeServer
resources, err := client.GetResourcesWithFilters(ctx, p, req)
if err != nil {
return nil, trace.Wrap(err)
}
kss, err = types.ResourcesWithLabels(resources).AsKubeServers()
if err != nil {
return nil, trace.Wrap(err)
}
return extractAndSortKubeClusters(kss), nil
}
func extractAndSortKubeClusters(kss []types.KubeServer) []types.KubeCluster {
uniqueClusters := make(map[string]types.KubeCluster)
for _, ks := range kss {
uniqueClusters[ks.GetName()] = ks.GetCluster()
}
kubeClusters := make([]types.KubeCluster, 0, len(uniqueClusters))
for _, cluster := range uniqueClusters {
kubeClusters = append(kubeClusters, cluster)
}
sorted := types.KubeClusters(kubeClusters)
sorted.SortByCustom(types.SortBy{
Field: types.ResourceMetadataName,
})
return []types.KubeCluster(sorted)
}
// CheckOrSetKubeCluster validates kubeClusterName if it's set, or a sane
// default based on registered clusters.
//
// If no clusters are registered, a NotFound error is returned.
func CheckOrSetKubeCluster(ctx context.Context, p KubeServicesPresence, kubeClusterName, teleportClusterName string) (string, error) {
kubeClusterNames, err := KubeClusterNames(ctx, p)
if err != nil {
return "", trace.Wrap(err)
}
if kubeClusterName != "" {
if !slices.Contains(kubeClusterNames, kubeClusterName) {
return "", trace.BadParameter("kubernetes cluster %q is not registered in this teleport cluster; you can list registered kubernetes clusters using 'tsh kube ls'", kubeClusterName)
}
return kubeClusterName, nil
}
// Default is the cluster with a name matching the Teleport cluster
// name (for backwards-compatibility with pre-5.0 behavior) or the
// first name alphabetically.
if len(kubeClusterNames) == 0 {
return "", trace.NotFound("no kubernetes clusters registered")
}
if slices.Contains(kubeClusterNames, teleportClusterName) {
return teleportClusterName, nil
}
return kubeClusterNames[0], nil
}