1/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package clientcmd
18
19import (
20	"fmt"
21	"io"
22	"io/ioutil"
23	"net/url"
24	"os"
25	"strings"
26
27	"github.com/imdario/mergo"
28	"k8s.io/klog"
29
30	restclient "k8s.io/client-go/rest"
31	clientauth "k8s.io/client-go/tools/auth"
32	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
33)
34
35var (
36	// ClusterDefaults has the same behavior as the old EnvVar and DefaultCluster fields
37	// DEPRECATED will be replaced
38	ClusterDefaults = clientcmdapi.Cluster{Server: getDefaultServer()}
39	// DefaultClientConfig represents the legacy behavior of this package for defaulting
40	// DEPRECATED will be replace
41	DefaultClientConfig = DirectClientConfig{*clientcmdapi.NewConfig(), "", &ConfigOverrides{
42		ClusterDefaults: ClusterDefaults,
43	}, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
44)
45
46// getDefaultServer returns a default setting for DefaultClientConfig
47// DEPRECATED
48func getDefaultServer() string {
49	if server := os.Getenv("KUBERNETES_MASTER"); len(server) > 0 {
50		return server
51	}
52	return "http://localhost:8080"
53}
54
55// ClientConfig is used to make it easy to get an api server client
56type ClientConfig interface {
57	// RawConfig returns the merged result of all overrides
58	RawConfig() (clientcmdapi.Config, error)
59	// ClientConfig returns a complete client config
60	ClientConfig() (*restclient.Config, error)
61	// Namespace returns the namespace resulting from the merged
62	// result of all overrides and a boolean indicating if it was
63	// overridden
64	Namespace() (string, bool, error)
65	// ConfigAccess returns the rules for loading/persisting the config.
66	ConfigAccess() ConfigAccess
67}
68
69type PersistAuthProviderConfigForUser func(user string) restclient.AuthProviderConfigPersister
70
71type promptedCredentials struct {
72	username string
73	password string
74}
75
76// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
77type DirectClientConfig struct {
78	config         clientcmdapi.Config
79	contextName    string
80	overrides      *ConfigOverrides
81	fallbackReader io.Reader
82	configAccess   ConfigAccess
83	// promptedCredentials store the credentials input by the user
84	promptedCredentials promptedCredentials
85}
86
87// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
88func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig {
89	return &DirectClientConfig{config, config.CurrentContext, overrides, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
90}
91
92// NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
93func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) ClientConfig {
94	return &DirectClientConfig{config, contextName, overrides, nil, configAccess, promptedCredentials{}}
95}
96
97// NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
98func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) ClientConfig {
99	return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess, promptedCredentials{}}
100}
101
102// NewClientConfigFromBytes takes your kubeconfig and gives you back a ClientConfig
103func NewClientConfigFromBytes(configBytes []byte) (ClientConfig, error) {
104	config, err := Load(configBytes)
105	if err != nil {
106		return nil, err
107	}
108
109	return &DirectClientConfig{*config, "", &ConfigOverrides{}, nil, nil, promptedCredentials{}}, nil
110}
111
112// RESTConfigFromKubeConfig is a convenience method to give back a restconfig from your kubeconfig bytes.
113// For programmatic access, this is what you want 80% of the time
114func RESTConfigFromKubeConfig(configBytes []byte) (*restclient.Config, error) {
115	clientConfig, err := NewClientConfigFromBytes(configBytes)
116	if err != nil {
117		return nil, err
118	}
119	return clientConfig.ClientConfig()
120}
121
122func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
123	return config.config, nil
124}
125
126// ClientConfig implements ClientConfig
127func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
128	// check that getAuthInfo, getContext, and getCluster do not return an error.
129	// Do this before checking if the current config is usable in the event that an
130	// AuthInfo, Context, or Cluster config with user-defined names are not found.
131	// This provides a user with the immediate cause for error if one is found
132	configAuthInfo, err := config.getAuthInfo()
133	if err != nil {
134		return nil, err
135	}
136
137	_, err = config.getContext()
138	if err != nil {
139		return nil, err
140	}
141
142	configClusterInfo, err := config.getCluster()
143	if err != nil {
144		return nil, err
145	}
146
147	if err := config.ConfirmUsable(); err != nil {
148		return nil, err
149	}
150
151	clientConfig := &restclient.Config{}
152	clientConfig.Host = configClusterInfo.Server
153
154	if len(config.overrides.Timeout) > 0 {
155		timeout, err := ParseTimeout(config.overrides.Timeout)
156		if err != nil {
157			return nil, err
158		}
159		clientConfig.Timeout = timeout
160	}
161
162	if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 {
163		u.RawQuery = ""
164		u.Fragment = ""
165		clientConfig.Host = u.String()
166	}
167	if len(configAuthInfo.Impersonate) > 0 {
168		clientConfig.Impersonate = restclient.ImpersonationConfig{
169			UserName: configAuthInfo.Impersonate,
170			Groups:   configAuthInfo.ImpersonateGroups,
171			Extra:    configAuthInfo.ImpersonateUserExtra,
172		}
173	}
174
175	// only try to read the auth information if we are secure
176	if restclient.IsConfigTransportTLS(*clientConfig) {
177		var err error
178		var persister restclient.AuthProviderConfigPersister
179		if config.configAccess != nil {
180			authInfoName, _ := config.getAuthInfoName()
181			persister = PersisterForUser(config.configAccess, authInfoName)
182		}
183		userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
184		if err != nil {
185			return nil, err
186		}
187		mergo.MergeWithOverwrite(clientConfig, userAuthPartialConfig)
188
189		serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
190		if err != nil {
191			return nil, err
192		}
193		mergo.MergeWithOverwrite(clientConfig, serverAuthPartialConfig)
194	}
195
196	return clientConfig, nil
197}
198
199// clientauth.Info object contain both user identification and server identification.  We want different precedence orders for
200// both, so we have to split the objects and merge them separately
201// we want this order of precedence for the server identification
202// 1.  configClusterInfo (the final result of command line flags and merged .kubeconfig files)
203// 2.  configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
204// 3.  load the ~/.kubernetes_auth file as a default
205func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
206	mergedConfig := &restclient.Config{}
207
208	// configClusterInfo holds the information identify the server provided by .kubeconfig
209	configClientConfig := &restclient.Config{}
210	configClientConfig.CAFile = configClusterInfo.CertificateAuthority
211	configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
212	configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
213	mergo.MergeWithOverwrite(mergedConfig, configClientConfig)
214
215	return mergedConfig, nil
216}
217
218// clientauth.Info object contain both user identification and server identification.  We want different precedence orders for
219// both, so we have to split the objects and merge them separately
220// we want this order of precedence for user identification
221// 1.  configAuthInfo minus auth-path (the final result of command line flags and merged .kubeconfig files)
222// 2.  configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
223// 3.  if there is not enough information to identify the user, load try the ~/.kubernetes_auth file
224// 4.  if there is not enough information to identify the user, prompt if possible
225func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister) (*restclient.Config, error) {
226	mergedConfig := &restclient.Config{}
227
228	// blindly overwrite existing values based on precedence
229	if len(configAuthInfo.Token) > 0 {
230		mergedConfig.BearerToken = configAuthInfo.Token
231		mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
232	} else if len(configAuthInfo.TokenFile) > 0 {
233		tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
234		if err != nil {
235			return nil, err
236		}
237		mergedConfig.BearerToken = string(tokenBytes)
238		mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
239	}
240	if len(configAuthInfo.Impersonate) > 0 {
241		mergedConfig.Impersonate = restclient.ImpersonationConfig{
242			UserName: configAuthInfo.Impersonate,
243			Groups:   configAuthInfo.ImpersonateGroups,
244			Extra:    configAuthInfo.ImpersonateUserExtra,
245		}
246	}
247	if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
248		mergedConfig.CertFile = configAuthInfo.ClientCertificate
249		mergedConfig.CertData = configAuthInfo.ClientCertificateData
250		mergedConfig.KeyFile = configAuthInfo.ClientKey
251		mergedConfig.KeyData = configAuthInfo.ClientKeyData
252	}
253	if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
254		mergedConfig.Username = configAuthInfo.Username
255		mergedConfig.Password = configAuthInfo.Password
256	}
257	if configAuthInfo.AuthProvider != nil {
258		mergedConfig.AuthProvider = configAuthInfo.AuthProvider
259		mergedConfig.AuthConfigPersister = persistAuthConfig
260	}
261	if configAuthInfo.Exec != nil {
262		mergedConfig.ExecProvider = configAuthInfo.Exec
263	}
264
265	// if there still isn't enough information to authenticate the user, try prompting
266	if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
267		if len(config.promptedCredentials.username) > 0 && len(config.promptedCredentials.password) > 0 {
268			mergedConfig.Username = config.promptedCredentials.username
269			mergedConfig.Password = config.promptedCredentials.password
270			return mergedConfig, nil
271		}
272		prompter := NewPromptingAuthLoader(fallbackReader)
273		promptedAuthInfo, err := prompter.Prompt()
274		if err != nil {
275			return nil, err
276		}
277		promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
278		previouslyMergedConfig := mergedConfig
279		mergedConfig = &restclient.Config{}
280		mergo.MergeWithOverwrite(mergedConfig, promptedConfig)
281		mergo.MergeWithOverwrite(mergedConfig, previouslyMergedConfig)
282		config.promptedCredentials.username = mergedConfig.Username
283		config.promptedCredentials.password = mergedConfig.Password
284	}
285
286	return mergedConfig, nil
287}
288
289// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
290func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
291	config := &restclient.Config{}
292	config.Username = info.User
293	config.Password = info.Password
294	config.CertFile = info.CertFile
295	config.KeyFile = info.KeyFile
296	config.BearerToken = info.BearerToken
297	return config
298}
299
300func canIdentifyUser(config restclient.Config) bool {
301	return len(config.Username) > 0 ||
302		(len(config.CertFile) > 0 || len(config.CertData) > 0) ||
303		len(config.BearerToken) > 0 ||
304		config.AuthProvider != nil ||
305		config.ExecProvider != nil
306}
307
308// Namespace implements ClientConfig
309func (config *DirectClientConfig) Namespace() (string, bool, error) {
310	if config.overrides != nil && config.overrides.Context.Namespace != "" {
311		// In the event we have an empty config but we do have a namespace override, we should return
312		// the namespace override instead of having config.ConfirmUsable() return an error. This allows
313		// things like in-cluster clients to execute `kubectl get pods --namespace=foo` and have the
314		// --namespace flag honored instead of being ignored.
315		return config.overrides.Context.Namespace, true, nil
316	}
317
318	if err := config.ConfirmUsable(); err != nil {
319		return "", false, err
320	}
321
322	configContext, err := config.getContext()
323	if err != nil {
324		return "", false, err
325	}
326
327	if len(configContext.Namespace) == 0 {
328		return "default", false, nil
329	}
330
331	return configContext.Namespace, false, nil
332}
333
334// ConfigAccess implements ClientConfig
335func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
336	return config.configAccess
337}
338
339// ConfirmUsable looks a particular context and determines if that particular part of the config is useable.  There might still be errors in the config,
340// but no errors in the sections requested or referenced.  It does not return early so that it can find as many errors as possible.
341func (config *DirectClientConfig) ConfirmUsable() error {
342	validationErrors := make([]error, 0)
343
344	var contextName string
345	if len(config.contextName) != 0 {
346		contextName = config.contextName
347	} else {
348		contextName = config.config.CurrentContext
349	}
350
351	if len(contextName) > 0 {
352		_, exists := config.config.Contexts[contextName]
353		if !exists {
354			validationErrors = append(validationErrors, &errContextNotFound{contextName})
355		}
356	}
357
358	authInfoName, _ := config.getAuthInfoName()
359	authInfo, _ := config.getAuthInfo()
360	validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...)
361	clusterName, _ := config.getClusterName()
362	cluster, _ := config.getCluster()
363	validationErrors = append(validationErrors, validateClusterInfo(clusterName, cluster)...)
364	// when direct client config is specified, and our only error is that no server is defined, we should
365	// return a standard "no config" error
366	if len(validationErrors) == 1 && validationErrors[0] == ErrEmptyCluster {
367		return newErrConfigurationInvalid([]error{ErrEmptyConfig})
368	}
369	return newErrConfigurationInvalid(validationErrors)
370}
371
372// getContextName returns the default, or user-set context name, and a boolean that indicates
373// whether the default context name has been overwritten by a user-set flag, or left as its default value
374func (config *DirectClientConfig) getContextName() (string, bool) {
375	if len(config.overrides.CurrentContext) != 0 {
376		return config.overrides.CurrentContext, true
377	}
378	if len(config.contextName) != 0 {
379		return config.contextName, false
380	}
381
382	return config.config.CurrentContext, false
383}
384
385// getAuthInfoName returns a string containing the current authinfo name for the current context,
386// and a boolean indicating  whether the default authInfo name is overwritten by a user-set flag, or
387// left as its default value
388func (config *DirectClientConfig) getAuthInfoName() (string, bool) {
389	if len(config.overrides.Context.AuthInfo) != 0 {
390		return config.overrides.Context.AuthInfo, true
391	}
392	context, _ := config.getContext()
393	return context.AuthInfo, false
394}
395
396// getClusterName returns a string containing the default, or user-set cluster name, and a boolean
397// indicating whether the default clusterName has been overwritten by a user-set flag, or left as
398// its default value
399func (config *DirectClientConfig) getClusterName() (string, bool) {
400	if len(config.overrides.Context.Cluster) != 0 {
401		return config.overrides.Context.Cluster, true
402	}
403	context, _ := config.getContext()
404	return context.Cluster, false
405}
406
407// getContext returns the clientcmdapi.Context, or an error if a required context is not found.
408func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
409	contexts := config.config.Contexts
410	contextName, required := config.getContextName()
411
412	mergedContext := clientcmdapi.NewContext()
413	if configContext, exists := contexts[contextName]; exists {
414		mergo.MergeWithOverwrite(mergedContext, configContext)
415	} else if required {
416		return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
417	}
418	mergo.MergeWithOverwrite(mergedContext, config.overrides.Context)
419
420	return *mergedContext, nil
421}
422
423// getAuthInfo returns the clientcmdapi.AuthInfo, or an error if a required auth info is not found.
424func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
425	authInfos := config.config.AuthInfos
426	authInfoName, required := config.getAuthInfoName()
427
428	mergedAuthInfo := clientcmdapi.NewAuthInfo()
429	if configAuthInfo, exists := authInfos[authInfoName]; exists {
430		mergo.MergeWithOverwrite(mergedAuthInfo, configAuthInfo)
431	} else if required {
432		return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
433	}
434	mergo.MergeWithOverwrite(mergedAuthInfo, config.overrides.AuthInfo)
435
436	return *mergedAuthInfo, nil
437}
438
439// getCluster returns the clientcmdapi.Cluster, or an error if a required cluster is not found.
440func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
441	clusterInfos := config.config.Clusters
442	clusterInfoName, required := config.getClusterName()
443
444	mergedClusterInfo := clientcmdapi.NewCluster()
445	mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterDefaults)
446	if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
447		mergo.MergeWithOverwrite(mergedClusterInfo, configClusterInfo)
448	} else if required {
449		return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
450	}
451	mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterInfo)
452	// An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
453	// otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
454	caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
455	caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData)
456	if config.overrides.ClusterInfo.InsecureSkipTLSVerify && caLen == 0 && caDataLen == 0 {
457		mergedClusterInfo.CertificateAuthority = ""
458		mergedClusterInfo.CertificateAuthorityData = nil
459	}
460
461	return *mergedClusterInfo, nil
462}
463
464// inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
465// Can take options overrides for flags explicitly provided to the command inside the cluster container.
466type inClusterClientConfig struct {
467	overrides               *ConfigOverrides
468	inClusterConfigProvider func() (*restclient.Config, error)
469}
470
471var _ ClientConfig = &inClusterClientConfig{}
472
473func (config *inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) {
474	return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters")
475}
476
477func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error) {
478	if config.inClusterConfigProvider == nil {
479		config.inClusterConfigProvider = restclient.InClusterConfig
480	}
481
482	icc, err := config.inClusterConfigProvider()
483	if err != nil {
484		return nil, err
485	}
486
487	// in-cluster configs only takes a host, token, or CA file
488	// if any of them were individually provided, overwrite anything else
489	if config.overrides != nil {
490		if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
491			icc.Host = server
492		}
493		if len(config.overrides.AuthInfo.Token) > 0 || len(config.overrides.AuthInfo.TokenFile) > 0 {
494			icc.BearerToken = config.overrides.AuthInfo.Token
495			icc.BearerTokenFile = config.overrides.AuthInfo.TokenFile
496		}
497		if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
498			icc.TLSClientConfig.CAFile = certificateAuthorityFile
499		}
500	}
501
502	return icc, err
503}
504
505func (config *inClusterClientConfig) Namespace() (string, bool, error) {
506	// This way assumes you've set the POD_NAMESPACE environment variable using the downward API.
507	// This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
508	if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
509		return ns, false, nil
510	}
511
512	// Fall back to the namespace associated with the service account token, if available
513	if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
514		if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
515			return ns, false, nil
516		}
517	}
518
519	return "default", false, nil
520}
521
522func (config *inClusterClientConfig) ConfigAccess() ConfigAccess {
523	return NewDefaultClientConfigLoadingRules()
524}
525
526// Possible returns true if loading an inside-kubernetes-cluster is possible.
527func (config *inClusterClientConfig) Possible() bool {
528	fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
529	return os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
530		os.Getenv("KUBERNETES_SERVICE_PORT") != "" &&
531		err == nil && !fi.IsDir()
532}
533
534// BuildConfigFromFlags is a helper function that builds configs from a master
535// url or a kubeconfig filepath. These are passed in as command line flags for cluster
536// components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
537// are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
538// to the default config.
539func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
540	if kubeconfigPath == "" && masterUrl == "" {
541		klog.Warningf("Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.")
542		kubeconfig, err := restclient.InClusterConfig()
543		if err == nil {
544			return kubeconfig, nil
545		}
546		klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
547	}
548	return NewNonInteractiveDeferredLoadingClientConfig(
549		&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
550		&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
551}
552
553// BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
554// url and a kubeconfigGetter.
555func BuildConfigFromKubeconfigGetter(masterUrl string, kubeconfigGetter KubeconfigGetter) (*restclient.Config, error) {
556	// TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
557	cc := NewNonInteractiveDeferredLoadingClientConfig(
558		&ClientConfigGetter{kubeconfigGetter: kubeconfigGetter},
559		&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}})
560	return cc.ClientConfig()
561}
562