2 Copyright 2014 The Kubernetes Authors.
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
27 "github.com/imdario/mergo"
30 restclient "k8s.io/client-go/rest"
31 clientauth "k8s.io/client-go/tools/auth"
32 clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
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{}}
46 // getDefaultServer returns a default setting for DefaultClientConfig
48 func getDefaultServer() string {
49 if server := os.Getenv("KUBERNETES_MASTER"); len(server) > 0 {
52 return "http://localhost:8080"
55 // ClientConfig is used to make it easy to get an api server client
56 type 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
64 Namespace() (string, bool, error)
65 // ConfigAccess returns the rules for loading/persisting the config.
66 ConfigAccess() ConfigAccess
69 type PersistAuthProviderConfigForUser func(user string) restclient.AuthProviderConfigPersister
71 type promptedCredentials struct {
76 // DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
77 type DirectClientConfig struct {
78 config clientcmdapi.Config
80 overrides *ConfigOverrides
81 fallbackReader io.Reader
82 configAccess ConfigAccess
83 // promptedCredentials store the credentials input by the user
84 promptedCredentials promptedCredentials
87 // NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
88 func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig {
89 return &DirectClientConfig{config, config.CurrentContext, overrides, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
92 // NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
93 func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) ClientConfig {
94 return &DirectClientConfig{config, contextName, overrides, nil, configAccess, promptedCredentials{}}
97 // NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
98 func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) ClientConfig {
99 return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess, promptedCredentials{}}
102 // NewClientConfigFromBytes takes your kubeconfig and gives you back a ClientConfig
103 func NewClientConfigFromBytes(configBytes []byte) (ClientConfig, error) {
104 config, err := Load(configBytes)
109 return &DirectClientConfig{*config, "", &ConfigOverrides{}, nil, nil, promptedCredentials{}}, nil
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
114 func RESTConfigFromKubeConfig(configBytes []byte) (*restclient.Config, error) {
115 clientConfig, err := NewClientConfigFromBytes(configBytes)
119 return clientConfig.ClientConfig()
122 func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
123 return config.config, nil
126 // ClientConfig implements ClientConfig
127 func (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()
137 _, err = config.getContext()
142 configClusterInfo, err := config.getCluster()
147 if err := config.ConfirmUsable(); err != nil {
151 clientConfig := &restclient.Config{}
152 clientConfig.Host = configClusterInfo.Server
154 if len(config.overrides.Timeout) > 0 {
155 timeout, err := ParseTimeout(config.overrides.Timeout)
159 clientConfig.Timeout = timeout
162 if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 {
165 clientConfig.Host = u.String()
167 if len(configAuthInfo.Impersonate) > 0 {
168 clientConfig.Impersonate = restclient.ImpersonationConfig{
169 UserName: configAuthInfo.Impersonate,
170 Groups: configAuthInfo.ImpersonateGroups,
171 Extra: configAuthInfo.ImpersonateUserExtra,
175 // only try to read the auth information if we are secure
176 if restclient.IsConfigTransportTLS(*clientConfig) {
178 var persister restclient.AuthProviderConfigPersister
179 if config.configAccess != nil {
180 authInfoName, _ := config.getAuthInfoName()
181 persister = PersisterForUser(config.configAccess, authInfoName)
183 userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
187 mergo.MergeWithOverwrite(clientConfig, userAuthPartialConfig)
189 serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
193 mergo.MergeWithOverwrite(clientConfig, serverAuthPartialConfig)
196 return clientConfig, nil
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
205 func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
206 mergedConfig := &restclient.Config{}
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)
215 return mergedConfig, nil
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
225 func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister) (*restclient.Config, error) {
226 mergedConfig := &restclient.Config{}
228 // blindly overwrite existing values based on precedence
229 if len(configAuthInfo.Token) > 0 {
230 mergedConfig.BearerToken = configAuthInfo.Token
231 } else if len(configAuthInfo.TokenFile) > 0 {
232 tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
236 mergedConfig.BearerToken = string(tokenBytes)
237 mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
239 if len(configAuthInfo.Impersonate) > 0 {
240 mergedConfig.Impersonate = restclient.ImpersonationConfig{
241 UserName: configAuthInfo.Impersonate,
242 Groups: configAuthInfo.ImpersonateGroups,
243 Extra: configAuthInfo.ImpersonateUserExtra,
246 if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
247 mergedConfig.CertFile = configAuthInfo.ClientCertificate
248 mergedConfig.CertData = configAuthInfo.ClientCertificateData
249 mergedConfig.KeyFile = configAuthInfo.ClientKey
250 mergedConfig.KeyData = configAuthInfo.ClientKeyData
252 if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
253 mergedConfig.Username = configAuthInfo.Username
254 mergedConfig.Password = configAuthInfo.Password
256 if configAuthInfo.AuthProvider != nil {
257 mergedConfig.AuthProvider = configAuthInfo.AuthProvider
258 mergedConfig.AuthConfigPersister = persistAuthConfig
260 if configAuthInfo.Exec != nil {
261 mergedConfig.ExecProvider = configAuthInfo.Exec
264 // if there still isn't enough information to authenticate the user, try prompting
265 if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
266 if len(config.promptedCredentials.username) > 0 && len(config.promptedCredentials.password) > 0 {
267 mergedConfig.Username = config.promptedCredentials.username
268 mergedConfig.Password = config.promptedCredentials.password
269 return mergedConfig, nil
271 prompter := NewPromptingAuthLoader(fallbackReader)
272 promptedAuthInfo, err := prompter.Prompt()
276 promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
277 previouslyMergedConfig := mergedConfig
278 mergedConfig = &restclient.Config{}
279 mergo.MergeWithOverwrite(mergedConfig, promptedConfig)
280 mergo.MergeWithOverwrite(mergedConfig, previouslyMergedConfig)
281 config.promptedCredentials.username = mergedConfig.Username
282 config.promptedCredentials.password = mergedConfig.Password
285 return mergedConfig, nil
288 // makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
289 func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
290 config := &restclient.Config{}
291 config.Username = info.User
292 config.Password = info.Password
293 config.CertFile = info.CertFile
294 config.KeyFile = info.KeyFile
295 config.BearerToken = info.BearerToken
299 // makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only server identification information
300 func makeServerIdentificationConfig(info clientauth.Info) restclient.Config {
301 config := restclient.Config{}
302 config.CAFile = info.CAFile
303 if info.Insecure != nil {
304 config.Insecure = *info.Insecure
309 func canIdentifyUser(config restclient.Config) bool {
310 return len(config.Username) > 0 ||
311 (len(config.CertFile) > 0 || len(config.CertData) > 0) ||
312 len(config.BearerToken) > 0 ||
313 config.AuthProvider != nil ||
314 config.ExecProvider != nil
317 // Namespace implements ClientConfig
318 func (config *DirectClientConfig) Namespace() (string, bool, error) {
319 if config.overrides != nil && config.overrides.Context.Namespace != "" {
320 // In the event we have an empty config but we do have a namespace override, we should return
321 // the namespace override instead of having config.ConfirmUsable() return an error. This allows
322 // things like in-cluster clients to execute `kubectl get pods --namespace=foo` and have the
323 // --namespace flag honored instead of being ignored.
324 return config.overrides.Context.Namespace, true, nil
327 if err := config.ConfirmUsable(); err != nil {
328 return "", false, err
331 configContext, err := config.getContext()
333 return "", false, err
336 if len(configContext.Namespace) == 0 {
337 return "default", false, nil
340 return configContext.Namespace, false, nil
343 // ConfigAccess implements ClientConfig
344 func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
345 return config.configAccess
348 // ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
349 // but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
350 func (config *DirectClientConfig) ConfirmUsable() error {
351 validationErrors := make([]error, 0)
353 var contextName string
354 if len(config.contextName) != 0 {
355 contextName = config.contextName
357 contextName = config.config.CurrentContext
360 if len(contextName) > 0 {
361 _, exists := config.config.Contexts[contextName]
363 validationErrors = append(validationErrors, &errContextNotFound{contextName})
367 authInfoName, _ := config.getAuthInfoName()
368 authInfo, _ := config.getAuthInfo()
369 validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...)
370 clusterName, _ := config.getClusterName()
371 cluster, _ := config.getCluster()
372 validationErrors = append(validationErrors, validateClusterInfo(clusterName, cluster)...)
373 // when direct client config is specified, and our only error is that no server is defined, we should
374 // return a standard "no config" error
375 if len(validationErrors) == 1 && validationErrors[0] == ErrEmptyCluster {
376 return newErrConfigurationInvalid([]error{ErrEmptyConfig})
378 return newErrConfigurationInvalid(validationErrors)
381 // getContextName returns the default, or user-set context name, and a boolean that indicates
382 // whether the default context name has been overwritten by a user-set flag, or left as its default value
383 func (config *DirectClientConfig) getContextName() (string, bool) {
384 if len(config.overrides.CurrentContext) != 0 {
385 return config.overrides.CurrentContext, true
387 if len(config.contextName) != 0 {
388 return config.contextName, false
391 return config.config.CurrentContext, false
394 // getAuthInfoName returns a string containing the current authinfo name for the current context,
395 // and a boolean indicating whether the default authInfo name is overwritten by a user-set flag, or
396 // left as its default value
397 func (config *DirectClientConfig) getAuthInfoName() (string, bool) {
398 if len(config.overrides.Context.AuthInfo) != 0 {
399 return config.overrides.Context.AuthInfo, true
401 context, _ := config.getContext()
402 return context.AuthInfo, false
405 // getClusterName returns a string containing the default, or user-set cluster name, and a boolean
406 // indicating whether the default clusterName has been overwritten by a user-set flag, or left as
408 func (config *DirectClientConfig) getClusterName() (string, bool) {
409 if len(config.overrides.Context.Cluster) != 0 {
410 return config.overrides.Context.Cluster, true
412 context, _ := config.getContext()
413 return context.Cluster, false
416 // getContext returns the clientcmdapi.Context, or an error if a required context is not found.
417 func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
418 contexts := config.config.Contexts
419 contextName, required := config.getContextName()
421 mergedContext := clientcmdapi.NewContext()
422 if configContext, exists := contexts[contextName]; exists {
423 mergo.MergeWithOverwrite(mergedContext, configContext)
425 return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
427 mergo.MergeWithOverwrite(mergedContext, config.overrides.Context)
429 return *mergedContext, nil
432 // getAuthInfo returns the clientcmdapi.AuthInfo, or an error if a required auth info is not found.
433 func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
434 authInfos := config.config.AuthInfos
435 authInfoName, required := config.getAuthInfoName()
437 mergedAuthInfo := clientcmdapi.NewAuthInfo()
438 if configAuthInfo, exists := authInfos[authInfoName]; exists {
439 mergo.MergeWithOverwrite(mergedAuthInfo, configAuthInfo)
441 return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
443 mergo.MergeWithOverwrite(mergedAuthInfo, config.overrides.AuthInfo)
445 return *mergedAuthInfo, nil
448 // getCluster returns the clientcmdapi.Cluster, or an error if a required cluster is not found.
449 func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
450 clusterInfos := config.config.Clusters
451 clusterInfoName, required := config.getClusterName()
453 mergedClusterInfo := clientcmdapi.NewCluster()
454 mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterDefaults)
455 if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
456 mergo.MergeWithOverwrite(mergedClusterInfo, configClusterInfo)
458 return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
460 mergo.MergeWithOverwrite(mergedClusterInfo, config.overrides.ClusterInfo)
461 // An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
462 // otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
463 caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
464 caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData)
465 if config.overrides.ClusterInfo.InsecureSkipTLSVerify && caLen == 0 && caDataLen == 0 {
466 mergedClusterInfo.CertificateAuthority = ""
467 mergedClusterInfo.CertificateAuthorityData = nil
470 return *mergedClusterInfo, nil
473 // inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
474 // Can take options overrides for flags explicitly provided to the command inside the cluster container.
475 type inClusterClientConfig struct {
476 overrides *ConfigOverrides
477 inClusterConfigProvider func() (*restclient.Config, error)
480 var _ ClientConfig = &inClusterClientConfig{}
482 func (config *inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) {
483 return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters")
486 func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error) {
487 if config.inClusterConfigProvider == nil {
488 config.inClusterConfigProvider = restclient.InClusterConfig
491 icc, err := config.inClusterConfigProvider()
496 // in-cluster configs only takes a host, token, or CA file
497 // if any of them were individually provided, overwrite anything else
498 if config.overrides != nil {
499 if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
502 if token := config.overrides.AuthInfo.Token; len(token) > 0 {
503 icc.BearerToken = token
505 if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
506 icc.TLSClientConfig.CAFile = certificateAuthorityFile
513 func (config *inClusterClientConfig) Namespace() (string, bool, error) {
514 // This way assumes you've set the POD_NAMESPACE environment variable using the downward API.
515 // This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
516 if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
517 return ns, false, nil
520 // Fall back to the namespace associated with the service account token, if available
521 if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
522 if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
523 return ns, false, nil
527 return "default", false, nil
530 func (config *inClusterClientConfig) ConfigAccess() ConfigAccess {
531 return NewDefaultClientConfigLoadingRules()
534 // Possible returns true if loading an inside-kubernetes-cluster is possible.
535 func (config *inClusterClientConfig) Possible() bool {
536 fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
537 return os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
538 os.Getenv("KUBERNETES_SERVICE_PORT") != "" &&
539 err == nil && !fi.IsDir()
542 // BuildConfigFromFlags is a helper function that builds configs from a master
543 // url or a kubeconfig filepath. These are passed in as command line flags for cluster
544 // components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
545 // are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
546 // to the default config.
547 func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
548 if kubeconfigPath == "" && masterUrl == "" {
549 klog.Warningf("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
550 kubeconfig, err := restclient.InClusterConfig()
552 return kubeconfig, nil
554 klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
556 return NewNonInteractiveDeferredLoadingClientConfig(
557 &ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
558 &ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
561 // BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
562 // url and a kubeconfigGetter.
563 func BuildConfigFromKubeconfigGetter(masterUrl string, kubeconfigGetter KubeconfigGetter) (*restclient.Config, error) {
564 // TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
565 cc := NewNonInteractiveDeferredLoadingClientConfig(
566 &ClientConfigGetter{kubeconfigGetter: kubeconfigGetter},
567 &ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}})
568 return cc.ClientConfig()