client_config.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package clientcmd
  14. import (
  15. "fmt"
  16. "io"
  17. "io/ioutil"
  18. "net/url"
  19. "os"
  20. "strings"
  21. "github.com/golang/glog"
  22. "github.com/imdario/mergo"
  23. "k8s.io/kubernetes/pkg/api"
  24. "k8s.io/kubernetes/pkg/client/restclient"
  25. clientauth "k8s.io/kubernetes/pkg/client/unversioned/auth"
  26. clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
  27. )
  28. var (
  29. // DefaultCluster is the cluster config used when no other config is specified
  30. // TODO: eventually apiserver should start on 443 and be secure by default
  31. DefaultCluster = clientcmdapi.Cluster{Server: "http://localhost:8080"}
  32. // EnvVarCluster allows overriding the DefaultCluster using an envvar for the server name
  33. EnvVarCluster = clientcmdapi.Cluster{Server: os.Getenv("KUBERNETES_MASTER")}
  34. DefaultClientConfig = DirectClientConfig{*clientcmdapi.NewConfig(), "", &ConfigOverrides{}, nil, NewDefaultClientConfigLoadingRules()}
  35. )
  36. // ClientConfig is used to make it easy to get an api server client
  37. type ClientConfig interface {
  38. // RawConfig returns the merged result of all overrides
  39. RawConfig() (clientcmdapi.Config, error)
  40. // ClientConfig returns a complete client config
  41. ClientConfig() (*restclient.Config, error)
  42. // Namespace returns the namespace resulting from the merged
  43. // result of all overrides and a boolean indicating if it was
  44. // overridden
  45. Namespace() (string, bool, error)
  46. // ConfigAccess returns the rules for loading/persisting the config.
  47. ConfigAccess() ConfigAccess
  48. }
  49. type PersistAuthProviderConfigForUser func(user string) restclient.AuthProviderConfigPersister
  50. // DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
  51. type DirectClientConfig struct {
  52. config clientcmdapi.Config
  53. contextName string
  54. overrides *ConfigOverrides
  55. fallbackReader io.Reader
  56. configAccess ConfigAccess
  57. }
  58. // NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
  59. func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig {
  60. return &DirectClientConfig{config, config.CurrentContext, overrides, nil, NewDefaultClientConfigLoadingRules()}
  61. }
  62. // NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
  63. func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) ClientConfig {
  64. return &DirectClientConfig{config, contextName, overrides, nil, configAccess}
  65. }
  66. // NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
  67. func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) ClientConfig {
  68. return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess}
  69. }
  70. func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
  71. return config.config, nil
  72. }
  73. // ClientConfig implements ClientConfig
  74. func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
  75. if err := config.ConfirmUsable(); err != nil {
  76. return nil, err
  77. }
  78. configAuthInfo := config.getAuthInfo()
  79. configClusterInfo := config.getCluster()
  80. clientConfig := &restclient.Config{}
  81. clientConfig.Host = configClusterInfo.Server
  82. if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 {
  83. u.RawQuery = ""
  84. u.Fragment = ""
  85. clientConfig.Host = u.String()
  86. }
  87. if len(configAuthInfo.Impersonate) > 0 {
  88. clientConfig.Impersonate = configAuthInfo.Impersonate
  89. }
  90. // only try to read the auth information if we are secure
  91. if restclient.IsConfigTransportTLS(*clientConfig) {
  92. var err error
  93. // mergo is a first write wins for map value and a last writing wins for interface values
  94. // NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a.
  95. // Our mergo.Merge version is older than this change.
  96. var persister restclient.AuthProviderConfigPersister
  97. if config.configAccess != nil {
  98. persister = PersisterForUser(config.configAccess, config.getAuthInfoName())
  99. }
  100. userAuthPartialConfig, err := getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister)
  101. if err != nil {
  102. return nil, err
  103. }
  104. mergo.Merge(clientConfig, userAuthPartialConfig)
  105. serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
  106. if err != nil {
  107. return nil, err
  108. }
  109. mergo.Merge(clientConfig, serverAuthPartialConfig)
  110. }
  111. return clientConfig, nil
  112. }
  113. // clientauth.Info object contain both user identification and server identification. We want different precedence orders for
  114. // both, so we have to split the objects and merge them separately
  115. // we want this order of precedence for the server identification
  116. // 1. configClusterInfo (the final result of command line flags and merged .kubeconfig files)
  117. // 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
  118. // 3. load the ~/.kubernetes_auth file as a default
  119. func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
  120. mergedConfig := &restclient.Config{}
  121. // configClusterInfo holds the information identify the server provided by .kubeconfig
  122. configClientConfig := &restclient.Config{}
  123. configClientConfig.CAFile = configClusterInfo.CertificateAuthority
  124. configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
  125. configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
  126. mergo.Merge(mergedConfig, configClientConfig)
  127. return mergedConfig, nil
  128. }
  129. // clientauth.Info object contain both user identification and server identification. We want different precedence orders for
  130. // both, so we have to split the objects and merge them separately
  131. // we want this order of precedence for user identifcation
  132. // 1. configAuthInfo minus auth-path (the final result of command line flags and merged .kubeconfig files)
  133. // 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
  134. // 3. if there is not enough information to idenfity the user, load try the ~/.kubernetes_auth file
  135. // 4. if there is not enough information to identify the user, prompt if possible
  136. func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister) (*restclient.Config, error) {
  137. mergedConfig := &restclient.Config{}
  138. // blindly overwrite existing values based on precedence
  139. if len(configAuthInfo.Token) > 0 {
  140. mergedConfig.BearerToken = configAuthInfo.Token
  141. } else if len(configAuthInfo.TokenFile) > 0 {
  142. tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
  143. if err != nil {
  144. return nil, err
  145. }
  146. mergedConfig.BearerToken = string(tokenBytes)
  147. }
  148. if len(configAuthInfo.Impersonate) > 0 {
  149. mergedConfig.Impersonate = configAuthInfo.Impersonate
  150. }
  151. if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
  152. mergedConfig.CertFile = configAuthInfo.ClientCertificate
  153. mergedConfig.CertData = configAuthInfo.ClientCertificateData
  154. mergedConfig.KeyFile = configAuthInfo.ClientKey
  155. mergedConfig.KeyData = configAuthInfo.ClientKeyData
  156. }
  157. if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
  158. mergedConfig.Username = configAuthInfo.Username
  159. mergedConfig.Password = configAuthInfo.Password
  160. }
  161. if configAuthInfo.AuthProvider != nil {
  162. mergedConfig.AuthProvider = configAuthInfo.AuthProvider
  163. mergedConfig.AuthConfigPersister = persistAuthConfig
  164. }
  165. // if there still isn't enough information to authenticate the user, try prompting
  166. if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
  167. prompter := NewPromptingAuthLoader(fallbackReader)
  168. promptedAuthInfo := prompter.Prompt()
  169. promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
  170. previouslyMergedConfig := mergedConfig
  171. mergedConfig = &restclient.Config{}
  172. mergo.Merge(mergedConfig, promptedConfig)
  173. mergo.Merge(mergedConfig, previouslyMergedConfig)
  174. }
  175. return mergedConfig, nil
  176. }
  177. // makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
  178. func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
  179. config := &restclient.Config{}
  180. config.Username = info.User
  181. config.Password = info.Password
  182. config.CertFile = info.CertFile
  183. config.KeyFile = info.KeyFile
  184. config.BearerToken = info.BearerToken
  185. return config
  186. }
  187. // makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only server identification information
  188. func makeServerIdentificationConfig(info clientauth.Info) restclient.Config {
  189. config := restclient.Config{}
  190. config.CAFile = info.CAFile
  191. if info.Insecure != nil {
  192. config.Insecure = *info.Insecure
  193. }
  194. return config
  195. }
  196. func canIdentifyUser(config restclient.Config) bool {
  197. return len(config.Username) > 0 ||
  198. (len(config.CertFile) > 0 || len(config.CertData) > 0) ||
  199. len(config.BearerToken) > 0 ||
  200. config.AuthProvider != nil
  201. }
  202. // Namespace implements ClientConfig
  203. func (config *DirectClientConfig) Namespace() (string, bool, error) {
  204. if err := config.ConfirmUsable(); err != nil {
  205. return "", false, err
  206. }
  207. configContext := config.getContext()
  208. if len(configContext.Namespace) == 0 {
  209. return api.NamespaceDefault, false, nil
  210. }
  211. overridden := false
  212. if config.overrides != nil && config.overrides.Context.Namespace != "" {
  213. overridden = true
  214. }
  215. return configContext.Namespace, overridden, nil
  216. }
  217. // ConfigAccess implements ClientConfig
  218. func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
  219. return config.configAccess
  220. }
  221. // ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
  222. // but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
  223. func (config *DirectClientConfig) ConfirmUsable() error {
  224. validationErrors := make([]error, 0)
  225. validationErrors = append(validationErrors, validateAuthInfo(config.getAuthInfoName(), config.getAuthInfo())...)
  226. validationErrors = append(validationErrors, validateClusterInfo(config.getClusterName(), config.getCluster())...)
  227. // when direct client config is specified, and our only error is that no server is defined, we should
  228. // return a standard "no config" error
  229. if len(validationErrors) == 1 && validationErrors[0] == ErrEmptyCluster {
  230. return newErrConfigurationInvalid([]error{ErrEmptyConfig})
  231. }
  232. return newErrConfigurationInvalid(validationErrors)
  233. }
  234. func (config *DirectClientConfig) getContextName() string {
  235. if len(config.overrides.CurrentContext) != 0 {
  236. return config.overrides.CurrentContext
  237. }
  238. if len(config.contextName) != 0 {
  239. return config.contextName
  240. }
  241. return config.config.CurrentContext
  242. }
  243. func (config *DirectClientConfig) getAuthInfoName() string {
  244. if len(config.overrides.Context.AuthInfo) != 0 {
  245. return config.overrides.Context.AuthInfo
  246. }
  247. return config.getContext().AuthInfo
  248. }
  249. func (config *DirectClientConfig) getClusterName() string {
  250. if len(config.overrides.Context.Cluster) != 0 {
  251. return config.overrides.Context.Cluster
  252. }
  253. return config.getContext().Cluster
  254. }
  255. func (config *DirectClientConfig) getContext() clientcmdapi.Context {
  256. contexts := config.config.Contexts
  257. contextName := config.getContextName()
  258. var mergedContext clientcmdapi.Context
  259. if configContext, exists := contexts[contextName]; exists {
  260. mergo.Merge(&mergedContext, configContext)
  261. }
  262. mergo.Merge(&mergedContext, config.overrides.Context)
  263. return mergedContext
  264. }
  265. func (config *DirectClientConfig) getAuthInfo() clientcmdapi.AuthInfo {
  266. authInfos := config.config.AuthInfos
  267. authInfoName := config.getAuthInfoName()
  268. var mergedAuthInfo clientcmdapi.AuthInfo
  269. if configAuthInfo, exists := authInfos[authInfoName]; exists {
  270. mergo.Merge(&mergedAuthInfo, configAuthInfo)
  271. }
  272. mergo.Merge(&mergedAuthInfo, config.overrides.AuthInfo)
  273. return mergedAuthInfo
  274. }
  275. func (config *DirectClientConfig) getCluster() clientcmdapi.Cluster {
  276. clusterInfos := config.config.Clusters
  277. clusterInfoName := config.getClusterName()
  278. var mergedClusterInfo clientcmdapi.Cluster
  279. mergo.Merge(&mergedClusterInfo, config.overrides.ClusterDefaults)
  280. mergo.Merge(&mergedClusterInfo, EnvVarCluster)
  281. if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
  282. mergo.Merge(&mergedClusterInfo, configClusterInfo)
  283. }
  284. mergo.Merge(&mergedClusterInfo, config.overrides.ClusterInfo)
  285. // An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
  286. // otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
  287. caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
  288. caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData)
  289. if config.overrides.ClusterInfo.InsecureSkipTLSVerify && caLen == 0 && caDataLen == 0 {
  290. mergedClusterInfo.CertificateAuthority = ""
  291. mergedClusterInfo.CertificateAuthorityData = nil
  292. }
  293. return mergedClusterInfo
  294. }
  295. // inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
  296. type inClusterClientConfig struct{}
  297. func (inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) {
  298. return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters")
  299. }
  300. func (inClusterClientConfig) ClientConfig() (*restclient.Config, error) {
  301. return restclient.InClusterConfig()
  302. }
  303. func (inClusterClientConfig) Namespace() (string, error) {
  304. // This way assumes you've set the POD_NAMESPACE environment variable using the downward API.
  305. // This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
  306. if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
  307. return ns, nil
  308. }
  309. // Fall back to the namespace associated with the service account token, if available
  310. if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
  311. if ns := strings.TrimSpace(string(data)); len(ns) > 0 {
  312. return ns, nil
  313. }
  314. }
  315. return "default", nil
  316. }
  317. func (inClusterClientConfig) ConfigAccess() ConfigAccess {
  318. return NewDefaultClientConfigLoadingRules()
  319. }
  320. // Possible returns true if loading an inside-kubernetes-cluster is possible.
  321. func (inClusterClientConfig) Possible() bool {
  322. fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token")
  323. return os.Getenv("KUBERNETES_SERVICE_HOST") != "" &&
  324. os.Getenv("KUBERNETES_SERVICE_PORT") != "" &&
  325. err == nil && !fi.IsDir()
  326. }
  327. // BuildConfigFromFlags is a helper function that builds configs from a master
  328. // url or a kubeconfig filepath. These are passed in as command line flags for cluster
  329. // components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
  330. // are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
  331. // to the default config.
  332. func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
  333. if kubeconfigPath == "" && masterUrl == "" {
  334. glog.Warningf("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
  335. kubeconfig, err := restclient.InClusterConfig()
  336. if err == nil {
  337. return kubeconfig, nil
  338. }
  339. glog.Warning("error creating inClusterConfig, falling back to default config: ", err)
  340. }
  341. return NewNonInteractiveDeferredLoadingClientConfig(
  342. &ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
  343. &ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
  344. }
  345. // BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
  346. // url and a kubeconfigGetter.
  347. func BuildConfigFromKubeconfigGetter(masterUrl string, kubeconfigGetter KubeconfigGetter) (*restclient.Config, error) {
  348. // TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
  349. cc := NewNonInteractiveDeferredLoadingClientConfig(
  350. &ClientConfigGetter{kubeconfigGetter: kubeconfigGetter},
  351. &ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}})
  352. return cc.ClientConfig()
  353. }