Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automated cherry pick of #17155: chore: generate kubeconfig on the fly #17227

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
84 changes: 64 additions & 20 deletions cmd/kops/util/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ limitations under the License.
package util

import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"

"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
Expand All @@ -36,6 +37,8 @@ import (
"k8s.io/kops/pkg/client/simple"
"k8s.io/kops/pkg/client/simple/api"
"k8s.io/kops/pkg/client/simple/vfsclientset"
"k8s.io/kops/pkg/kubeconfig"
"k8s.io/kops/upup/pkg/fi/cloudup"
"k8s.io/kops/util/pkg/vfs"
)

Expand All @@ -57,7 +60,8 @@ type Factory struct {

// clusterInfo holds REST connection configuration for connecting to a cluster
type clusterInfo struct {
clusterName string
factory *Factory
cluster *kops.Cluster

cachedHTTPClient *http.Client
cachedRESTConfig *rest.Config
Expand Down Expand Up @@ -155,48 +159,47 @@ func (f *Factory) KopsStateStore() string {
return f.options.RegistryPath
}

func (f *Factory) getClusterInfo(clusterName string) *clusterInfo {
func (f *Factory) getClusterInfo(cluster *kops.Cluster) *clusterInfo {
f.mutex.Lock()
defer f.mutex.Unlock()

if clusterInfo, ok := f.clusters[clusterName]; ok {
key := cluster.ObjectMeta.Name
if clusterInfo, ok := f.clusters[key]; ok {
return clusterInfo
}
clusterInfo := &clusterInfo{}
f.clusters[clusterName] = clusterInfo
clusterInfo := &clusterInfo{
factory: f,
cluster: cluster,
}
f.clusters[key] = clusterInfo
return clusterInfo
}

func (f *Factory) RESTConfig(cluster *kops.Cluster) (*rest.Config, error) {
clusterInfo := f.getClusterInfo(cluster.ObjectMeta.Name)
clusterInfo := f.getClusterInfo(cluster)
return clusterInfo.RESTConfig()
}

func (f *clusterInfo) RESTConfig() (*rest.Config, error) {
if f.cachedRESTConfig == nil {
// Get the kubeconfig from the context

clientGetter := genericclioptions.NewConfigFlags(true)
if f.clusterName != "" {
contextName := f.clusterName
clientGetter.Context = &contextName
}
ctx := context.Background()

restConfig, err := clientGetter.ToRESTConfig()
if f.cachedRESTConfig == nil {
restConfig, err := f.factory.buildRESTConfig(ctx, f.cluster)
if err != nil {
return nil, fmt.Errorf("loading kubecfg settings for %q: %w", f.clusterName, err)
return nil, err
}

restConfig.UserAgent = "kops"
restConfig.Burst = 50
restConfig.QPS = 20

f.cachedRESTConfig = restConfig
}
return f.cachedRESTConfig, nil
}

func (f *Factory) HTTPClient(cluster *kops.Cluster) (*http.Client, error) {
clusterInfo := f.getClusterInfo(cluster.ObjectMeta.Name)
clusterInfo := f.getClusterInfo(cluster)
return clusterInfo.HTTPClient()
}

Expand All @@ -216,8 +219,8 @@ func (f *clusterInfo) HTTPClient() (*http.Client, error) {
}

// DynamicClient returns a dynamic client
func (f *Factory) DynamicClient(clusterName string) (dynamic.Interface, error) {
clusterInfo := f.getClusterInfo(clusterName)
func (f *Factory) DynamicClient(cluster *kops.Cluster) (dynamic.Interface, error) {
clusterInfo := f.getClusterInfo(cluster)
return clusterInfo.DynamicClient()
}

Expand Down Expand Up @@ -249,3 +252,44 @@ func (f *Factory) VFSContext() *vfs.VFSContext {
}
return f.vfsContext
}

func (f *Factory) buildRESTConfig(ctx context.Context, cluster *kops.Cluster) (*rest.Config, error) {
clientset, err := f.KopsClient()
if err != nil {
return nil, err
}

keyStore, err := clientset.KeyStore(cluster)
if err != nil {
return nil, err
}

secretStore, err := clientset.SecretStore(cluster)
if err != nil {
return nil, err
}

cloud, err := cloudup.BuildCloud(cluster)
if err != nil {
return nil, err
}

// Generate a relatively short-lived certificate / kubeconfig
createKubecfgOptions := kubeconfig.CreateKubecfgOptions{
Admin: 1 * time.Hour,
}

conf, err := kubeconfig.BuildKubecfg(
ctx,
cluster,
keyStore,
secretStore,
cloud,
createKubecfgOptions,
f.KopsStateStore())
if err != nil {
return nil, err
}

return conf.ToRESTConfig()
}
13 changes: 0 additions & 13 deletions cmd/kops/validate_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,6 @@ func RunValidateCluster(ctx context.Context, f *util.Factory, out io.Writer, opt
return nil, fmt.Errorf("no InstanceGroup objects found")
}

// // TODO: Refactor into util.Factory
// contextName := cluster.ObjectMeta.Name
// configLoadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// if options.kubeconfig != "" {
// configLoadingRules.ExplicitPath = options.kubeconfig
// }
// config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
// configLoadingRules,
// &clientcmd.ConfigOverrides{CurrentContext: contextName}).ClientConfig()
// if err != nil {
// return nil, fmt.Errorf("cannot load kubecfg settings for %q: %v", contextName, err)
// }

restConfig, err := f.RESTConfig(cluster)
if err != nil {
return nil, fmt.Errorf("getting rest config: %w", err)
Expand Down
12 changes: 9 additions & 3 deletions pkg/kubeconfig/create_kubecfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,15 @@ const DefaultKubecfgAdminLifetime = 18 * time.Hour

type CreateKubecfgOptions struct {
CreateKubecfg bool
Admin time.Duration
User string
Internal bool

// Admin is the lifetime of the admin certificate
Admin time.Duration

// User is the user to use in the kubeconfig
User string

// Internal is whether to use the internal API endpoint
Internal bool

// UseKopsAuthenticationPlugin controls whether we should use the kOps auth helper instead of a static credential
UseKopsAuthenticationPlugin bool
Expand Down
19 changes: 19 additions & 0 deletions pkg/kubeconfig/kubecfg_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package kubeconfig
import (
"fmt"

"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/klog/v2"
Expand Down Expand Up @@ -208,3 +209,21 @@ func (b *KubeconfigBuilder) WriteKubecfg(configAccess clientcmd.ConfigAccess) er
fmt.Printf("kOps has set your kubectl context to %s\n", b.Context)
return nil
}

func (b *KubeconfigBuilder) ToRESTConfig() (*rest.Config, error) {
restConfig := &rest.Config{}

restConfig.Host = b.Server
restConfig.TLSClientConfig.CAData = b.CACerts
restConfig.TLSClientConfig.ServerName = b.TLSServerName

usingAuthPlugin := len(b.AuthenticationExec) != 0
if usingAuthPlugin {
return nil, fmt.Errorf("auth plugin not yet supported by ToRESTConfig")
}

restConfig.CertData = b.ClientCert
restConfig.KeyData = b.ClientKey

return restConfig, nil
}
Loading