2015-01-07 20:59:22 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-01-07 20:59:22 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package clientcmd
import (
2015-06-01 23:58:19 +00:00
"fmt"
2015-01-07 20:59:22 +00:00
"io"
2016-02-11 19:46:56 +00:00
"io/ioutil"
2015-06-03 04:04:07 +00:00
"net/url"
2015-01-07 20:59:22 +00:00
"os"
2016-02-11 19:46:56 +00:00
"strings"
2015-01-07 20:59:22 +00:00
2015-11-06 18:34:49 +00:00
"github.com/golang/glog"
2015-01-07 20:59:22 +00:00
"github.com/imdario/mergo"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
2016-02-12 18:58:43 +00:00
"k8s.io/kubernetes/pkg/client/restclient"
2015-08-12 17:35:07 +00:00
clientauth "k8s.io/kubernetes/pkg/client/unversioned/auth"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
2015-01-07 20:59:22 +00:00
)
var (
2015-01-21 15:38:58 +00:00
// DefaultCluster is the cluster config used when no other config is specified
2015-01-07 20:59:22 +00:00
// TODO: eventually apiserver should start on 443 and be secure by default
2015-01-21 15:38:58 +00:00
DefaultCluster = clientcmdapi . Cluster { Server : "http://localhost:8080" }
// EnvVarCluster allows overriding the DefaultCluster using an envvar for the server name
EnvVarCluster = clientcmdapi . Cluster { Server : os . Getenv ( "KUBERNETES_MASTER" ) }
2015-06-19 17:32:14 +00:00
2016-04-14 18:55:34 +00:00
DefaultClientConfig = DirectClientConfig { * clientcmdapi . NewConfig ( ) , "" , & ConfigOverrides { } , nil , NewDefaultClientConfigLoadingRules ( ) }
2015-01-07 20:59:22 +00:00
)
// ClientConfig is used to make it easy to get an api server client
type ClientConfig interface {
2015-01-02 18:08:37 +00:00
// RawConfig returns the merged result of all overrides
2014-12-17 13:03:03 +00:00
RawConfig ( ) ( clientcmdapi . Config , error )
2015-01-07 20:59:22 +00:00
// ClientConfig returns a complete client config
2016-02-12 18:58:43 +00:00
ClientConfig ( ) ( * restclient . Config , error )
2015-06-26 20:49:34 +00:00
// Namespace returns the namespace resulting from the merged
// result of all overrides and a boolean indicating if it was
// overridden
Namespace ( ) ( string , bool , error )
2016-04-14 18:55:34 +00:00
// ConfigAccess returns the rules for loading/persisting the config.
ConfigAccess ( ) ConfigAccess
2015-01-07 20:59:22 +00:00
}
2016-04-14 18:55:34 +00:00
type PersistAuthProviderConfigForUser func ( user string ) restclient . AuthProviderConfigPersister
2014-12-17 13:03:03 +00:00
// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
2015-01-07 20:59:22 +00:00
type DirectClientConfig struct {
2014-12-17 13:03:03 +00:00
config clientcmdapi . Config
2015-01-07 20:59:22 +00:00
contextName string
overrides * ConfigOverrides
fallbackReader io . Reader
2016-04-14 18:55:34 +00:00
configAccess ConfigAccess
2015-01-07 20:59:22 +00:00
}
// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
2014-12-17 13:03:03 +00:00
func NewDefaultClientConfig ( config clientcmdapi . Config , overrides * ConfigOverrides ) ClientConfig {
2016-04-14 18:55:34 +00:00
return & DirectClientConfig { config , config . CurrentContext , overrides , nil , NewDefaultClientConfigLoadingRules ( ) }
2015-01-07 20:59:22 +00:00
}
// NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
2016-04-14 18:55:34 +00:00
func NewNonInteractiveClientConfig ( config clientcmdapi . Config , contextName string , overrides * ConfigOverrides , configAccess ConfigAccess ) ClientConfig {
return & DirectClientConfig { config , contextName , overrides , nil , configAccess }
2015-01-07 20:59:22 +00:00
}
// NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
2016-04-14 18:55:34 +00:00
func NewInteractiveClientConfig ( config clientcmdapi . Config , contextName string , overrides * ConfigOverrides , fallbackReader io . Reader , configAccess ConfigAccess ) ClientConfig {
return & DirectClientConfig { config , contextName , overrides , fallbackReader , configAccess }
2015-01-07 20:59:22 +00:00
}
2016-02-15 15:42:38 +00:00
func ( config * DirectClientConfig ) RawConfig ( ) ( clientcmdapi . Config , error ) {
2014-12-17 13:03:03 +00:00
return config . config , nil
}
2015-01-07 20:59:22 +00:00
// ClientConfig implements ClientConfig
2016-02-12 18:58:43 +00:00
func ( config * DirectClientConfig ) ClientConfig ( ) ( * restclient . Config , error ) {
2015-01-07 20:59:22 +00:00
if err := config . ConfirmUsable ( ) ; err != nil {
return nil , err
}
configAuthInfo := config . getAuthInfo ( )
configClusterInfo := config . getCluster ( )
2016-02-12 18:58:43 +00:00
clientConfig := & restclient . Config { }
2015-01-07 20:59:22 +00:00
clientConfig . Host = configClusterInfo . Server
2015-06-03 04:04:07 +00:00
if u , err := url . ParseRequestURI ( clientConfig . Host ) ; err == nil && u . Opaque == "" && len ( u . Path ) > 1 {
u . RawQuery = ""
u . Fragment = ""
clientConfig . Host = u . String ( )
}
2016-03-28 18:33:58 +00:00
if len ( configAuthInfo . Impersonate ) > 0 {
clientConfig . Impersonate = configAuthInfo . Impersonate
}
2015-01-07 20:59:22 +00:00
// only try to read the auth information if we are secure
2016-02-12 18:58:43 +00:00
if restclient . IsConfigTransportTLS ( * clientConfig ) {
2015-01-07 20:59:22 +00:00
var err error
2015-01-08 13:40:02 +00:00
// mergo is a first write wins for map value and a last writing wins for interface values
2016-02-04 07:02:05 +00:00
// NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a.
// Our mergo.Merge version is older than this change.
2016-04-14 18:55:34 +00:00
var persister restclient . AuthProviderConfigPersister
if config . configAccess != nil {
persister = PersisterForUser ( config . configAccess , config . getAuthInfoName ( ) )
}
userAuthPartialConfig , err := getUserIdentificationPartialConfig ( configAuthInfo , config . fallbackReader , persister )
2015-01-08 13:40:02 +00:00
if err != nil {
return nil , err
2015-01-07 20:59:22 +00:00
}
2015-01-08 13:40:02 +00:00
mergo . Merge ( clientConfig , userAuthPartialConfig )
2015-01-07 20:59:22 +00:00
2015-01-08 13:40:02 +00:00
serverAuthPartialConfig , err := getServerIdentificationPartialConfig ( configAuthInfo , configClusterInfo )
if err != nil {
return nil , err
2015-01-07 20:59:22 +00:00
}
2015-01-08 13:40:02 +00:00
mergo . Merge ( clientConfig , serverAuthPartialConfig )
}
return clientConfig , nil
}
2015-01-07 20:59:22 +00:00
2015-01-08 13:40:02 +00:00
// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
// both, so we have to split the objects and merge them separately
// we want this order of precedence for the server identification
// 1. configClusterInfo (the final result of command line flags and merged .kubeconfig files)
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
// 3. load the ~/.kubernetes_auth file as a default
2016-02-12 18:58:43 +00:00
func getServerIdentificationPartialConfig ( configAuthInfo clientcmdapi . AuthInfo , configClusterInfo clientcmdapi . Cluster ) ( * restclient . Config , error ) {
mergedConfig := & restclient . Config { }
2015-01-08 13:40:02 +00:00
// configClusterInfo holds the information identify the server provided by .kubeconfig
2016-02-12 18:58:43 +00:00
configClientConfig := & restclient . Config { }
2015-01-08 13:40:02 +00:00
configClientConfig . CAFile = configClusterInfo . CertificateAuthority
2015-02-18 02:37:43 +00:00
configClientConfig . CAData = configClusterInfo . CertificateAuthorityData
2015-01-08 13:40:02 +00:00
configClientConfig . Insecure = configClusterInfo . InsecureSkipTLSVerify
mergo . Merge ( mergedConfig , configClientConfig )
return mergedConfig , nil
}
2015-01-07 20:59:22 +00:00
2015-01-08 13:40:02 +00:00
// clientauth.Info object contain both user identification and server identification. We want different precedence orders for
// both, so we have to split the objects and merge them separately
// we want this order of precedence for user identifcation
// 1. configAuthInfo minus auth-path (the final result of command line flags and merged .kubeconfig files)
// 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority)
// 3. if there is not enough information to idenfity the user, load try the ~/.kubernetes_auth file
// 4. if there is not enough information to identify the user, prompt if possible
2016-04-14 18:55:34 +00:00
func getUserIdentificationPartialConfig ( configAuthInfo clientcmdapi . AuthInfo , fallbackReader io . Reader , persistAuthConfig restclient . AuthProviderConfigPersister ) ( * restclient . Config , error ) {
2016-02-12 18:58:43 +00:00
mergedConfig := & restclient . Config { }
2015-01-08 13:40:02 +00:00
// blindly overwrite existing values based on precedence
if len ( configAuthInfo . Token ) > 0 {
mergedConfig . BearerToken = configAuthInfo . Token
}
2016-03-28 18:33:58 +00:00
if len ( configAuthInfo . Impersonate ) > 0 {
mergedConfig . Impersonate = configAuthInfo . Impersonate
}
2015-02-18 02:37:43 +00:00
if len ( configAuthInfo . ClientCertificate ) > 0 || len ( configAuthInfo . ClientCertificateData ) > 0 {
2015-01-08 13:40:02 +00:00
mergedConfig . CertFile = configAuthInfo . ClientCertificate
2015-02-18 02:37:43 +00:00
mergedConfig . CertData = configAuthInfo . ClientCertificateData
2015-01-08 13:40:02 +00:00
mergedConfig . KeyFile = configAuthInfo . ClientKey
2015-02-18 02:37:43 +00:00
mergedConfig . KeyData = configAuthInfo . ClientKeyData
}
if len ( configAuthInfo . Username ) > 0 || len ( configAuthInfo . Password ) > 0 {
mergedConfig . Username = configAuthInfo . Username
mergedConfig . Password = configAuthInfo . Password
2015-01-08 13:40:02 +00:00
}
2016-03-06 02:00:36 +00:00
if configAuthInfo . AuthProvider != nil {
mergedConfig . AuthProvider = configAuthInfo . AuthProvider
2016-04-14 18:55:34 +00:00
mergedConfig . AuthConfigPersister = persistAuthConfig
2016-03-06 02:00:36 +00:00
}
2015-01-08 13:40:02 +00:00
// if there still isn't enough information to authenticate the user, try prompting
if ! canIdentifyUser ( * mergedConfig ) && ( fallbackReader != nil ) {
prompter := NewPromptingAuthLoader ( fallbackReader )
promptedAuthInfo := prompter . Prompt ( )
promptedConfig := makeUserIdentificationConfig ( * promptedAuthInfo )
previouslyMergedConfig := mergedConfig
2016-02-12 18:58:43 +00:00
mergedConfig = & restclient . Config { }
2015-01-08 13:40:02 +00:00
mergo . Merge ( mergedConfig , promptedConfig )
mergo . Merge ( mergedConfig , previouslyMergedConfig )
}
return mergedConfig , nil
}
// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
2016-02-12 18:58:43 +00:00
func makeUserIdentificationConfig ( info clientauth . Info ) * restclient . Config {
config := & restclient . Config { }
2015-01-08 13:40:02 +00:00
config . Username = info . User
config . Password = info . Password
config . CertFile = info . CertFile
config . KeyFile = info . KeyFile
config . BearerToken = info . BearerToken
return config
}
// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only server identification information
2016-02-12 18:58:43 +00:00
func makeServerIdentificationConfig ( info clientauth . Info ) restclient . Config {
config := restclient . Config { }
2015-01-08 13:40:02 +00:00
config . CAFile = info . CAFile
if info . Insecure != nil {
config . Insecure = * info . Insecure
}
return config
}
2016-02-12 18:58:43 +00:00
func canIdentifyUser ( config restclient . Config ) bool {
2015-01-08 13:40:02 +00:00
return len ( config . Username ) > 0 ||
2015-02-18 02:37:43 +00:00
( len ( config . CertFile ) > 0 || len ( config . CertData ) > 0 ) ||
2016-03-06 02:00:36 +00:00
len ( config . BearerToken ) > 0 ||
config . AuthProvider != nil
2015-01-07 20:59:22 +00:00
}
2016-04-14 18:55:34 +00:00
// Namespace implements ClientConfig
2016-02-15 15:42:38 +00:00
func ( config * DirectClientConfig ) Namespace ( ) ( string , bool , error ) {
2015-01-02 18:08:37 +00:00
if err := config . ConfirmUsable ( ) ; err != nil {
2015-06-26 20:49:34 +00:00
return "" , false , err
2015-01-02 18:08:37 +00:00
}
configContext := config . getContext ( )
2015-01-23 13:13:32 +00:00
if len ( configContext . Namespace ) == 0 {
2015-06-26 20:49:34 +00:00
return api . NamespaceDefault , false , nil
2015-01-02 18:08:37 +00:00
}
2015-06-26 20:49:34 +00:00
overridden := false
if config . overrides != nil && config . overrides . Context . Namespace != "" {
overridden = true
}
return configContext . Namespace , overridden , nil
2015-01-02 18:08:37 +00:00
}
2016-04-14 18:55:34 +00:00
// ConfigAccess implements ClientConfig
func ( config * DirectClientConfig ) ConfigAccess ( ) ConfigAccess {
return config . configAccess
}
2015-01-07 20:59:22 +00:00
// ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
// but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
2016-02-15 15:42:38 +00:00
func ( config * DirectClientConfig ) ConfirmUsable ( ) error {
2015-01-07 20:59:22 +00:00
validationErrors := make ( [ ] error , 0 )
validationErrors = append ( validationErrors , validateAuthInfo ( config . getAuthInfoName ( ) , config . getAuthInfo ( ) ) ... )
validationErrors = append ( validationErrors , validateClusterInfo ( config . getClusterName ( ) , config . getCluster ( ) ) ... )
2015-11-05 19:59:22 +00:00
// when direct client config is specified, and our only error is that no server is defined, we should
// return a standard "no config" error
if len ( validationErrors ) == 1 && validationErrors [ 0 ] == ErrEmptyCluster {
return newErrConfigurationInvalid ( [ ] error { ErrEmptyConfig } )
}
2015-04-02 05:38:25 +00:00
return newErrConfigurationInvalid ( validationErrors )
2015-01-07 20:59:22 +00:00
}
2016-02-15 15:42:38 +00:00
func ( config * DirectClientConfig ) getContextName ( ) string {
2015-01-07 20:59:22 +00:00
if len ( config . overrides . CurrentContext ) != 0 {
return config . overrides . CurrentContext
}
if len ( config . contextName ) != 0 {
return config . contextName
}
return config . config . CurrentContext
}
2016-02-15 15:42:38 +00:00
func ( config * DirectClientConfig ) getAuthInfoName ( ) string {
2015-01-02 18:08:37 +00:00
if len ( config . overrides . Context . AuthInfo ) != 0 {
return config . overrides . Context . AuthInfo
2015-01-07 20:59:22 +00:00
}
return config . getContext ( ) . AuthInfo
}
2016-02-15 15:42:38 +00:00
func ( config * DirectClientConfig ) getClusterName ( ) string {
2015-01-02 18:08:37 +00:00
if len ( config . overrides . Context . Cluster ) != 0 {
return config . overrides . Context . Cluster
2015-01-07 20:59:22 +00:00
}
return config . getContext ( ) . Cluster
}
2016-02-15 15:42:38 +00:00
func ( config * DirectClientConfig ) getContext ( ) clientcmdapi . Context {
2015-01-02 18:08:37 +00:00
contexts := config . config . Contexts
contextName := config . getContextName ( )
var mergedContext clientcmdapi . Context
if configContext , exists := contexts [ contextName ] ; exists {
mergo . Merge ( & mergedContext , configContext )
}
mergo . Merge ( & mergedContext , config . overrides . Context )
return mergedContext
2015-01-07 20:59:22 +00:00
}
2016-02-15 15:42:38 +00:00
func ( config * DirectClientConfig ) getAuthInfo ( ) clientcmdapi . AuthInfo {
2015-01-07 20:59:22 +00:00
authInfos := config . config . AuthInfos
authInfoName := config . getAuthInfoName ( )
2014-12-17 13:03:03 +00:00
var mergedAuthInfo clientcmdapi . AuthInfo
2015-01-07 20:59:22 +00:00
if configAuthInfo , exists := authInfos [ authInfoName ] ; exists {
mergo . Merge ( & mergedAuthInfo , configAuthInfo )
}
mergo . Merge ( & mergedAuthInfo , config . overrides . AuthInfo )
return mergedAuthInfo
}
2016-02-15 15:42:38 +00:00
func ( config * DirectClientConfig ) getCluster ( ) clientcmdapi . Cluster {
2015-01-07 20:59:22 +00:00
clusterInfos := config . config . Clusters
clusterInfoName := config . getClusterName ( )
2014-12-17 13:03:03 +00:00
var mergedClusterInfo clientcmdapi . Cluster
2015-01-21 15:38:58 +00:00
mergo . Merge ( & mergedClusterInfo , DefaultCluster )
mergo . Merge ( & mergedClusterInfo , EnvVarCluster )
2015-01-07 20:59:22 +00:00
if configClusterInfo , exists := clusterInfos [ clusterInfoName ] ; exists {
mergo . Merge ( & mergedClusterInfo , configClusterInfo )
}
mergo . Merge ( & mergedClusterInfo , config . overrides . ClusterInfo )
2016-03-17 20:35:16 +00:00
// An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data
// otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set"
caLen := len ( config . overrides . ClusterInfo . CertificateAuthority )
caDataLen := len ( config . overrides . ClusterInfo . CertificateAuthorityData )
if config . overrides . ClusterInfo . InsecureSkipTLSVerify && caLen == 0 && caDataLen == 0 {
mergedClusterInfo . CertificateAuthority = ""
mergedClusterInfo . CertificateAuthorityData = nil
}
2015-01-07 20:59:22 +00:00
return mergedClusterInfo
}
2015-06-01 23:58:19 +00:00
// inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
type inClusterClientConfig struct { }
func ( inClusterClientConfig ) RawConfig ( ) ( clientcmdapi . Config , error ) {
return clientcmdapi . Config { } , fmt . Errorf ( "inCluster environment config doesn't support multiple clusters" )
}
2016-02-12 18:58:43 +00:00
func ( inClusterClientConfig ) ClientConfig ( ) ( * restclient . Config , error ) {
return restclient . InClusterConfig ( )
2015-06-01 23:58:19 +00:00
}
func ( inClusterClientConfig ) Namespace ( ) ( string , error ) {
2016-02-11 19:46:56 +00:00
// This way assumes you've set the POD_NAMESPACE environment variable using the downward API.
// This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
2015-06-01 23:58:19 +00:00
if ns := os . Getenv ( "POD_NAMESPACE" ) ; ns != "" {
return ns , nil
}
2016-02-11 19:46:56 +00:00
// Fall back to the namespace associated with the service account token, if available
if data , err := ioutil . ReadFile ( "/var/run/secrets/kubernetes.io/serviceaccount/namespace" ) ; err == nil {
if ns := strings . TrimSpace ( string ( data ) ) ; len ( ns ) > 0 {
return ns , nil
}
}
2015-06-01 23:58:19 +00:00
return "default" , nil
}
2016-04-14 18:55:34 +00:00
func ( inClusterClientConfig ) ConfigAccess ( ) ConfigAccess {
return NewDefaultClientConfigLoadingRules ( )
}
2015-06-01 23:58:19 +00:00
// Possible returns true if loading an inside-kubernetes-cluster is possible.
func ( inClusterClientConfig ) Possible ( ) bool {
fi , err := os . Stat ( "/var/run/secrets/kubernetes.io/serviceaccount/token" )
return os . Getenv ( "KUBERNETES_SERVICE_HOST" ) != "" &&
os . Getenv ( "KUBERNETES_SERVICE_PORT" ) != "" &&
err == nil && ! fi . IsDir ( )
}
2015-11-06 18:34:49 +00:00
// BuildConfigFromFlags is a helper function that builds configs from a master
// url or a kubeconfig filepath. These are passed in as command line flags for cluster
// components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
// are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
// to the default config.
2016-02-12 18:58:43 +00:00
func BuildConfigFromFlags ( masterUrl , kubeconfigPath string ) ( * restclient . Config , error ) {
2015-11-06 18:34:49 +00:00
if kubeconfigPath == "" && masterUrl == "" {
glog . Warningf ( "Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work." )
2016-02-12 18:58:43 +00:00
kubeconfig , err := restclient . InClusterConfig ( )
2015-11-06 18:34:49 +00:00
if err == nil {
return kubeconfig , nil
}
2016-01-20 06:32:09 +00:00
glog . Warning ( "error creating inClusterConfig, falling back to default config: " , err )
2015-11-06 18:34:49 +00:00
}
return NewNonInteractiveDeferredLoadingClientConfig (
& ClientConfigLoadingRules { ExplicitPath : kubeconfigPath } ,
& ConfigOverrides { ClusterInfo : clientcmdapi . Cluster { Server : masterUrl } } ) . ClientConfig ( )
}
2016-05-24 03:20:27 +00:00
// BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
// url and a kubeconfigGetter.
func BuildConfigFromKubeconfigGetter ( masterUrl string , kubeconfigGetter KubeconfigGetter ) ( * restclient . Config , error ) {
2016-05-24 07:27:39 +00:00
// TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
2016-05-24 03:20:27 +00:00
cc := NewNonInteractiveDeferredLoadingClientConfig (
& ClientConfigGetter { kubeconfigGetter : kubeconfigGetter } ,
& ConfigOverrides { ClusterInfo : clientcmdapi . Cluster { Server : masterUrl } } )
return cc . ClientConfig ( )
}