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/golang/glog"
28	"github.com/imdario/mergo"
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
179		// mergo is a first write wins for map value and a last writing wins for interface values
180		// NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a.
181		//       Our mergo.Merge version is older than this change.
182		var persister restclient.AuthProviderConfigPersister
183		if config.configAccess != nil {
184			authInfoName, _ := config.getAuthInfoName()
185			persister = PersisterForUser(config.configAccess, authInfoName)
186		}
187		userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
188		if err != nil {
189			return nil, err
190		}
191		mergo.Merge(clientConfig, userAuthPartialConfig)
192
193		serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
194		if err != nil {
195			return nil, err
196		}
197		mergo.Merge(clientConfig, serverAuthPartialConfig)
198	}
199
200	return clientConfig, nil
201}
202
203// clientauth.Info object contain both user identification and server identification.  We want different precedence orders for
204// both, so we have to split the objects and merge them separately
205// we want this order of precedence for the server identification
206// 1.  configClusterInfo (the final result of command line flags and merged .kubeconfig files)
207// 2.  configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
208// 3.  load the ~/.kubernetes_auth file as a default
209func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
210	mergedConfig := &restclient.Config{}
211
212	// configClusterInfo holds the information identify the server provided by .kubeconfig
213	configClientConfig := &restclient.Config{}
214	configClientConfig.CAFile = configClusterInfo.CertificateAuthority
215	configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
216	configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
217	mergo.Merge(mergedConfig, configClientConfig)
218
219	return mergedConfig, nil
220}
221
222// clientauth.Info object contain both user identification and server identification.  We want different precedence orders for
223// both, so we have to split the objects and merge them separately
224// we want this order of precedence for user identification
225// 1.  configAuthInfo minus auth-path (the final result of command line flags and merged .kubeconfig files)
226// 2.  configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
227// 3.  if there is not enough information to identify the user, load try the ~/.kubernetes_auth file
228// 4.  if there is not enough information to identify the user, prompt if possible
229func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister) (*restclient.Config, error) {
230	mergedConfig := &restclient.Config{}
231
232	// blindly overwrite existing values based on precedence
233	if len(configAuthInfo.Token) > 0 {
234		mergedConfig.BearerToken = configAuthInfo.Token
235	} else if len(configAuthInfo.TokenFile) > 0 {
236		tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
237		if err != nil {
238			return nil, err
239		}
240		mergedConfig.BearerToken = string(tokenBytes)
241	}
242	if len(configAuthInfo.Impersonate) > 0 {
243		mergedConfig.Impersonate = restclient.ImpersonationConfig{
244			UserName: configAuthInfo.Impersonate,
245			Groups:   configAuthInfo.ImpersonateGroups,
246			Extra:    configAuthInfo.ImpersonateUserExtra,
247		}
248	}
249	if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
250		mergedConfig.CertFile = configAuthInfo.ClientCertificate
251		mergedConfig.CertData = configAuthInfo.ClientCertificateData
252		mergedConfig.KeyFile = configAuthInfo.ClientKey
253		mergedConfig.KeyData = configAuthInfo.ClientKeyData
254	}
255	if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
256		mergedConfig.Username = configAuthInfo.Username
257		mergedConfig.Password = configAuthInfo.Password
258	}
259	if configAuthInfo.AuthProvider != nil {
260		mergedConfig.AuthProvider = configAuthInfo.AuthProvider
261		mergedConfig.AuthConfigPersister = persistAuthConfig
262	}
263	if configAuthInfo.Exec != nil {
264		mergedConfig.ExecProvider = configAuthInfo.Exec
265	}
266
267	// if there still isn't enough information to authenticate the user, try prompting
268	if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
269		if len(config.promptedCredentials.username) > 0 && len(config.promptedCredentials.password) > 0 {
270			mergedConfig.Username = config.promptedCredentials.username
271			mergedConfig.Password = config.promptedCredentials.password
272			return mergedConfig, nil
273		}
274		prompter := NewPromptingAuthLoader(fallbackReader)
275		promptedAuthInfo, err := prompter.Prompt()
276		if err != nil {
277			return nil, err
278		}
279		promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
280		previouslyMergedConfig := mergedConfig
281		mergedConfig = &restclient.Config{}
282		mergo.Merge(mergedConfig, promptedConfig)
283		mergo.Merge(mergedConfig, previouslyMergedConfig)
284		config.promptedCredentials.username = mergedConfig.Username
285		config.promptedCredentials.password = mergedConfig.Password
286	}
287
288	return mergedConfig, nil
289}
290
291// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
292func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
293	config := &restclient.Config{}
294	config.Username = info.User
295	config.Password = info.Password
296	config.CertFile = info.CertFile
297	config.KeyFile = info.KeyFile
298	config.BearerToken = info.BearerToken
299	return config
300}
301
302// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only server identification information
303func makeServerIdentificationConfig(info clientauth.Info) restclient.Config {
304	config := restclient.Config{}
305	config.CAFile = info.CAFile
306	if info.Insecure != nil {
307		config.Insecure = *info.Insecure
308	}
309	return config
310}
311
312func canIdentifyUser(config restclient.Config) bool {
313	return len(config.Username) > 0 ||
314		(len(config.CertFile) > 0 || len(config.CertData) > 0) ||
315		len(config.BearerToken) > 0 ||
316		config.AuthProvider != nil ||
317		config.ExecProvider != nil
318}
319
320// Namespace implements ClientConfig
321func (config *DirectClientConfig) Namespace() (string, bool, error) {
322	if config.overrides != nil && config.overrides.Context.Namespace != "" {
323		// In the event we have an empty config but we do have a namespace override, we should return
324		// the namespace override instead of having config.ConfirmUsable() return an error. This allows
325		// things like in-cluster clients to execute `kubectl get pods --namespace=foo` and have the
326		// --namespace flag honored instead of being ignored.
327		return config.overrides.Context.Namespace, true, nil
328	}
329
330	if err := config.ConfirmUsable(); err != nil {
331		return "", false, err
332	}
333
334	configContext, err := config.getContext()
335	if err != nil {
336		return "", false, err
337	}
338
339	if len(configContext.Namespace) == 0 {
340		return "default", false, nil
341	}
342
343	return configContext.Namespace, false, nil
344}
345
346// ConfigAccess implements ClientConfig
347func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
348	return config.configAccess
349}
350
351// ConfirmUsable looks a particular context and determines if that particular part of the config is useable.  There might still be errors in the config,
352// but no errors in the sections requested or referenced.  It does not return early so that it can find as many errors as possible.
353func (config *DirectClientConfig) ConfirmUsable() error {
354	validationErrors := make([]error, 0)
355
356	var contextName string
357	if len(config.contextName) != 0 {
358		contextName = config.contextName
359	} else {
360		contextName = config.config.CurrentContext
361	}
362
363	if len(contextName) > 0 {
364		_, exists := config.config.Contexts[contextName]
365		if !exists {
366			validationErrors = append(validationErrors, &errContextNotFound{contextName})
367		}
368	}
369
370	authInfoName, _ := config.getAuthInfoName()
371	authInfo, _ := config.getAuthInfo()
372	validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...)
373	clusterName, _ := config.getClusterName()
374	cluster, _ := config.getCluster()
375	validationErrors = append(validationErrors, validateClusterInfo(clusterName, cluster)...)
376	// when direct client config is specified, and our only error is that no server is defined, we should
377	// return a standard "no config" error
378	if len(validationErrors) == 1 && validationErrors[0] == ErrEmptyCluster {
379		return newErrConfigurationInvalid([]error{ErrEmptyConfig})
380	}
381	return newErrConfigurationInvalid(validationErrors)
382}
383
384// getContextName returns the default, or user-set context name, and a boolean that indicates
385// whether the default context name has been overwritten by a user-set flag, or left as its default value
386func (config *DirectClientConfig) getContextName() (string, bool) {
387	if len(config.overrides.CurrentContext) != 0 {
388		return config.overrides.CurrentContext, true
389	}
390	if len(config.contextName) != 0 {
391		return config.contextName, false
392	}
393
394	return config.config.CurrentContext, false
395}
396
397// getAuthInfoName returns a string containing the current authinfo name for the current context,
398// and a boolean indicating  whether the default authInfo name is overwritten by a user-set flag, or
399// left as its default value
400func (config *DirectClientConfig) getAuthInfoName() (string, bool) {
401	if len(config.overrides.Context.AuthInfo) != 0 {
402		return config.overrides.Context.AuthInfo, true
403	}
404	context, _ := config.getContext()
405	return context.AuthInfo, false
406}
407
408// getClusterName returns a string containing the default, or user-set cluster name, and a boolean
409// indicating whether the default clusterName has been overwritten by a user-set flag, or left as
410// its default value
411func (config *DirectClientConfig) getClusterName() (string, bool) {
412	if len(config.overrides.Context.Cluster) != 0 {
413		return config.overrides.Context.Cluster, true
414	}
415	context, _ := config.getContext()
416	return context.Cluster, false
417}
418
419// getContext returns the clientcmdapi.Context, or an error if a required context is not found.
420func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
421	contexts := config.config.Contexts
422	contextName, required := config.getContextName()
423
424	mergedContext := clientcmdapi.NewContext()
425	if configContext, exists := contexts[contextName]; exists {
426		mergo.Merge(mergedContext, configContext)
427	} else if required {
428		return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
429	}
430	mergo.Merge(mergedContext, config.overrides.Context)
431
432	return *mergedContext, nil
433}
434
435// getAuthInfo returns the clientcmdapi.AuthInfo, or an error if a required auth info is not found.
436func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
437	authInfos := config.config.AuthInfos
438	authInfoName, required := config.getAuthInfoName()
439
440	mergedAuthInfo := clientcmdapi.NewAuthInfo()
441	if configAuthInfo, exists := authInfos[authInfoName]; exists {
442		mergo.Merge(mergedAuthInfo, configAuthInfo)
443	} else if required {
444		return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
445	}
446	mergo.Merge(mergedAuthInfo, config.overrides.AuthInfo)
447
448	return *mergedAuthInfo, nil
449}
450
451// getCluster returns the clientcmdapi.Cluster, or an error if a required cluster is not found.
452func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
453	clusterInfos := config.config.Clusters
454	clusterInfoName, required := config.getClusterName()
455
456	mergedClusterInfo := clientcmdapi.NewCluster()
457	mergo.Merge(mergedClusterInfo, config.overrides.ClusterDefaults)
458	if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
459		mergo.Merge(mergedClusterInfo, configClusterInfo)
460	} else if required {
461		return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
462	}
463	mergo.Merge(mergedClusterInfo, config.overrides.ClusterInfo)
464	// An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
465	// otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
466	caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
467	caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData)
468	if config.overrides.ClusterInfo.InsecureSkipTLSVerify && caLen == 0 && caDataLen == 0 {
469		mergedClusterInfo.CertificateAuthority = ""
470		mergedClusterInfo.CertificateAuthorityData = nil
471	}
472
473	return *mergedClusterInfo, nil
474}
475
476// inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
477// Can take options overrides for flags explicitly provided to the command inside the cluster container.
478type inClusterClientConfig struct {
479	overrides               *ConfigOverrides
480	inClusterConfigProvider func() (*restclient.Config, error)
481}
482
483var _ ClientConfig = &inClusterClientConfig{}
484
485func (config *inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) {
486	return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters")
487}
488
489func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error) {
490	if config.inClusterConfigProvider == nil {
491		config.inClusterConfigProvider = restclient.InClusterConfig
492	}
493
494	icc, err := config.inClusterConfigProvider()
495	if err != nil {
496		return nil, err
497	}
498
499	// in-cluster configs only takes a host, token, or CA file
500	// if any of them were individually provided, overwrite anything else
501	if config.overrides != nil {
502		if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
503			icc.Host = server
504		}
505		if token := config.overrides.AuthInfo.Token; len(token) > 0 {
506			icc.BearerToken = token
507		}
508		if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
509			icc.TLSClientConfig.CAFile = certificateAuthorityFile
510		}
511	}
512
513	return icc, err
514}
515
516func (config *inClusterClientConfig) Namespace() (string, bool, error) {
517	// This way assumes you've set the POD_NAMESPACE environment variable using the downward API.
518	// This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
519	if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
520		return ns, false, nil
521	}
522
523	// Fall back to the namespace associated with the service account token, if available
524	if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
525		if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
526			return ns, false, nil
527		}
528	}
529
530	return "default", false, nil
531}
532
533func (config *inClusterClientConfig) ConfigAccess() ConfigAccess {
534	return NewDefaultClientConfigLoadingRules()
535}
536
537// Possible returns true if loading an inside-kubernetes-cluster is possible.
538func (config *inClusterClientConfig) Possible() bool {
539	fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
540	return os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
541		os.Getenv("KUBERNETES_SERVICE_PORT") != "" &&
542		err == nil && !fi.IsDir()
543}
544
545// BuildConfigFromFlags is a helper function that builds configs from a master
546// url or a kubeconfig filepath. These are passed in as command line flags for cluster
547// components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
548// are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
549// to the default config.
550func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
551	if kubeconfigPath == "" && masterUrl == "" {
552		glog.Warningf("Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.")
553		kubeconfig, err := restclient.InClusterConfig()
554		if err == nil {
555			return kubeconfig, nil
556		}
557		glog.Warning("error creating inClusterConfig, falling back to default config: ", err)
558	}
559	return NewNonInteractiveDeferredLoadingClientConfig(
560		&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
561		&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
562}
563
564// BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
565// url and a kubeconfigGetter.
566func BuildConfigFromKubeconfigGetter(masterUrl string, kubeconfigGetter KubeconfigGetter) (*restclient.Config, error) {
567	// TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
568	cc := NewNonInteractiveDeferredLoadingClientConfig(
569		&ClientConfigGetter{kubeconfigGetter: kubeconfigGetter},
570		&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}})
571	return cc.ClientConfig()
572}
573