a3bf13711c
I had to run `make generate`. Some API functions got additional parameters `Options` and `Context`. I used empty options and `context.TODO()` for now. Signed-off-by: leonnicolas <leonloechner@gmx.de>
636 lines
25 KiB
Go
636 lines
25 KiB
Go
/*
|
|
Copyright 2014 The Kubernetes Authors.
|
|
|
|
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 (
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"unicode"
|
|
|
|
restclient "k8s.io/client-go/rest"
|
|
clientauth "k8s.io/client-go/tools/auth"
|
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
|
"k8s.io/klog/v2"
|
|
|
|
"github.com/imdario/mergo"
|
|
)
|
|
|
|
const (
|
|
// clusterExtensionKey is reserved in the cluster extensions list for exec plugin config.
|
|
clusterExtensionKey = "client.authentication.k8s.io/exec"
|
|
)
|
|
|
|
var (
|
|
// ClusterDefaults has the same behavior as the old EnvVar and DefaultCluster fields
|
|
// DEPRECATED will be replaced
|
|
ClusterDefaults = clientcmdapi.Cluster{Server: getDefaultServer()}
|
|
// DefaultClientConfig represents the legacy behavior of this package for defaulting
|
|
// DEPRECATED will be replace
|
|
DefaultClientConfig = DirectClientConfig{*clientcmdapi.NewConfig(), "", &ConfigOverrides{
|
|
ClusterDefaults: ClusterDefaults,
|
|
}, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
|
|
)
|
|
|
|
// getDefaultServer returns a default setting for DefaultClientConfig
|
|
// DEPRECATED
|
|
func getDefaultServer() string {
|
|
if server := os.Getenv("KUBERNETES_MASTER"); len(server) > 0 {
|
|
return server
|
|
}
|
|
return "http://localhost:8080"
|
|
}
|
|
|
|
// ClientConfig is used to make it easy to get an api server client
|
|
type ClientConfig interface {
|
|
// RawConfig returns the merged result of all overrides
|
|
RawConfig() (clientcmdapi.Config, error)
|
|
// ClientConfig returns a complete client config
|
|
ClientConfig() (*restclient.Config, error)
|
|
// Namespace returns the namespace resulting from the merged
|
|
// result of all overrides and a boolean indicating if it was
|
|
// overridden
|
|
Namespace() (string, bool, error)
|
|
// ConfigAccess returns the rules for loading/persisting the config.
|
|
ConfigAccess() ConfigAccess
|
|
}
|
|
|
|
type PersistAuthProviderConfigForUser func(user string) restclient.AuthProviderConfigPersister
|
|
|
|
type promptedCredentials struct {
|
|
username string
|
|
password string `datapolicy:"password"`
|
|
}
|
|
|
|
// DirectClientConfig is a ClientConfig interface that is backed by a clientcmdapi.Config, options overrides, and an optional fallbackReader for auth information
|
|
type DirectClientConfig struct {
|
|
config clientcmdapi.Config
|
|
contextName string
|
|
overrides *ConfigOverrides
|
|
fallbackReader io.Reader
|
|
configAccess ConfigAccess
|
|
// promptedCredentials store the credentials input by the user
|
|
promptedCredentials promptedCredentials
|
|
}
|
|
|
|
// NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name
|
|
func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig {
|
|
return &DirectClientConfig{config, config.CurrentContext, overrides, nil, NewDefaultClientConfigLoadingRules(), promptedCredentials{}}
|
|
}
|
|
|
|
// NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information
|
|
func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, configAccess ConfigAccess) ClientConfig {
|
|
return &DirectClientConfig{config, contextName, overrides, nil, configAccess, promptedCredentials{}}
|
|
}
|
|
|
|
// NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags
|
|
func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader, configAccess ConfigAccess) ClientConfig {
|
|
return &DirectClientConfig{config, contextName, overrides, fallbackReader, configAccess, promptedCredentials{}}
|
|
}
|
|
|
|
// NewClientConfigFromBytes takes your kubeconfig and gives you back a ClientConfig
|
|
func NewClientConfigFromBytes(configBytes []byte) (ClientConfig, error) {
|
|
config, err := Load(configBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &DirectClientConfig{*config, "", &ConfigOverrides{}, nil, nil, promptedCredentials{}}, nil
|
|
}
|
|
|
|
// RESTConfigFromKubeConfig is a convenience method to give back a restconfig from your kubeconfig bytes.
|
|
// For programmatic access, this is what you want 80% of the time
|
|
func RESTConfigFromKubeConfig(configBytes []byte) (*restclient.Config, error) {
|
|
clientConfig, err := NewClientConfigFromBytes(configBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return clientConfig.ClientConfig()
|
|
}
|
|
|
|
func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
|
return config.config, nil
|
|
}
|
|
|
|
// ClientConfig implements ClientConfig
|
|
func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) {
|
|
// check that getAuthInfo, getContext, and getCluster do not return an error.
|
|
// Do this before checking if the current config is usable in the event that an
|
|
// AuthInfo, Context, or Cluster config with user-defined names are not found.
|
|
// This provides a user with the immediate cause for error if one is found
|
|
configAuthInfo, err := config.getAuthInfo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = config.getContext()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
configClusterInfo, err := config.getCluster()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := config.ConfirmUsable(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
clientConfig := &restclient.Config{}
|
|
clientConfig.Host = configClusterInfo.Server
|
|
if configClusterInfo.ProxyURL != "" {
|
|
u, err := parseProxyURL(configClusterInfo.ProxyURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clientConfig.Proxy = http.ProxyURL(u)
|
|
}
|
|
|
|
if config.overrides != nil && len(config.overrides.Timeout) > 0 {
|
|
timeout, err := ParseTimeout(config.overrides.Timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clientConfig.Timeout = timeout
|
|
}
|
|
|
|
if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 {
|
|
u.RawQuery = ""
|
|
u.Fragment = ""
|
|
clientConfig.Host = u.String()
|
|
}
|
|
if len(configAuthInfo.Impersonate) > 0 {
|
|
clientConfig.Impersonate = restclient.ImpersonationConfig{
|
|
UserName: configAuthInfo.Impersonate,
|
|
Groups: configAuthInfo.ImpersonateGroups,
|
|
Extra: configAuthInfo.ImpersonateUserExtra,
|
|
}
|
|
}
|
|
|
|
// only try to read the auth information if we are secure
|
|
if restclient.IsConfigTransportTLS(*clientConfig) {
|
|
var err error
|
|
var persister restclient.AuthProviderConfigPersister
|
|
if config.configAccess != nil {
|
|
authInfoName, _ := config.getAuthInfoName()
|
|
persister = PersisterForUser(config.configAccess, authInfoName)
|
|
}
|
|
userAuthPartialConfig, err := config.getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader, persister, configClusterInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mergo.Merge(clientConfig, userAuthPartialConfig, mergo.WithOverride)
|
|
|
|
serverAuthPartialConfig, err := getServerIdentificationPartialConfig(configAuthInfo, configClusterInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mergo.Merge(clientConfig, serverAuthPartialConfig, mergo.WithOverride)
|
|
}
|
|
|
|
return clientConfig, nil
|
|
}
|
|
|
|
// 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
|
|
func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
|
|
mergedConfig := &restclient.Config{}
|
|
|
|
// configClusterInfo holds the information identify the server provided by .kubeconfig
|
|
configClientConfig := &restclient.Config{}
|
|
configClientConfig.CAFile = configClusterInfo.CertificateAuthority
|
|
configClientConfig.CAData = configClusterInfo.CertificateAuthorityData
|
|
configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify
|
|
configClientConfig.ServerName = configClusterInfo.TLSServerName
|
|
mergo.Merge(mergedConfig, configClientConfig, mergo.WithOverride)
|
|
|
|
return mergedConfig, nil
|
|
}
|
|
|
|
// 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 identification
|
|
// 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 identify the user, load try the ~/.kubernetes_auth file
|
|
// 4. if there is not enough information to identify the user, prompt if possible
|
|
func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader, persistAuthConfig restclient.AuthProviderConfigPersister, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) {
|
|
mergedConfig := &restclient.Config{}
|
|
|
|
// blindly overwrite existing values based on precedence
|
|
if len(configAuthInfo.Token) > 0 {
|
|
mergedConfig.BearerToken = configAuthInfo.Token
|
|
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
|
|
} else if len(configAuthInfo.TokenFile) > 0 {
|
|
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mergedConfig.BearerToken = string(tokenBytes)
|
|
mergedConfig.BearerTokenFile = configAuthInfo.TokenFile
|
|
}
|
|
if len(configAuthInfo.Impersonate) > 0 {
|
|
mergedConfig.Impersonate = restclient.ImpersonationConfig{
|
|
UserName: configAuthInfo.Impersonate,
|
|
Groups: configAuthInfo.ImpersonateGroups,
|
|
Extra: configAuthInfo.ImpersonateUserExtra,
|
|
}
|
|
}
|
|
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
|
|
mergedConfig.CertFile = configAuthInfo.ClientCertificate
|
|
mergedConfig.CertData = configAuthInfo.ClientCertificateData
|
|
mergedConfig.KeyFile = configAuthInfo.ClientKey
|
|
mergedConfig.KeyData = configAuthInfo.ClientKeyData
|
|
}
|
|
if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
|
|
mergedConfig.Username = configAuthInfo.Username
|
|
mergedConfig.Password = configAuthInfo.Password
|
|
}
|
|
if configAuthInfo.AuthProvider != nil {
|
|
mergedConfig.AuthProvider = configAuthInfo.AuthProvider
|
|
mergedConfig.AuthConfigPersister = persistAuthConfig
|
|
}
|
|
if configAuthInfo.Exec != nil {
|
|
mergedConfig.ExecProvider = configAuthInfo.Exec
|
|
mergedConfig.ExecProvider.InstallHint = cleanANSIEscapeCodes(mergedConfig.ExecProvider.InstallHint)
|
|
mergedConfig.ExecProvider.Config = configClusterInfo.Extensions[clusterExtensionKey]
|
|
}
|
|
|
|
// if there still isn't enough information to authenticate the user, try prompting
|
|
if !canIdentifyUser(*mergedConfig) && (fallbackReader != nil) {
|
|
if len(config.promptedCredentials.username) > 0 && len(config.promptedCredentials.password) > 0 {
|
|
mergedConfig.Username = config.promptedCredentials.username
|
|
mergedConfig.Password = config.promptedCredentials.password
|
|
return mergedConfig, nil
|
|
}
|
|
prompter := NewPromptingAuthLoader(fallbackReader)
|
|
promptedAuthInfo, err := prompter.Prompt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo)
|
|
previouslyMergedConfig := mergedConfig
|
|
mergedConfig = &restclient.Config{}
|
|
mergo.Merge(mergedConfig, promptedConfig, mergo.WithOverride)
|
|
mergo.Merge(mergedConfig, previouslyMergedConfig, mergo.WithOverride)
|
|
config.promptedCredentials.username = mergedConfig.Username
|
|
config.promptedCredentials.password = mergedConfig.Password
|
|
}
|
|
|
|
return mergedConfig, nil
|
|
}
|
|
|
|
// makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information
|
|
func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config {
|
|
config := &restclient.Config{}
|
|
config.Username = info.User
|
|
config.Password = info.Password
|
|
config.CertFile = info.CertFile
|
|
config.KeyFile = info.KeyFile
|
|
config.BearerToken = info.BearerToken
|
|
return config
|
|
}
|
|
|
|
func canIdentifyUser(config restclient.Config) bool {
|
|
return len(config.Username) > 0 ||
|
|
(len(config.CertFile) > 0 || len(config.CertData) > 0) ||
|
|
len(config.BearerToken) > 0 ||
|
|
config.AuthProvider != nil ||
|
|
config.ExecProvider != nil
|
|
}
|
|
|
|
// cleanANSIEscapeCodes takes an arbitrary string and ensures that there are no
|
|
// ANSI escape sequences that could put the terminal in a weird state (e.g.,
|
|
// "\e[1m" bolds text)
|
|
func cleanANSIEscapeCodes(s string) string {
|
|
// spaceControlCharacters includes tab, new line, vertical tab, new page, and
|
|
// carriage return. These are in the unicode.Cc category, but that category also
|
|
// contains ESC (U+001B) which we don't want.
|
|
spaceControlCharacters := unicode.RangeTable{
|
|
R16: []unicode.Range16{
|
|
{Lo: 0x0009, Hi: 0x000D, Stride: 1},
|
|
},
|
|
}
|
|
|
|
// Why not make this deny-only (instead of allow-only)? Because unicode.C
|
|
// contains newline and tab characters that we want.
|
|
allowedRanges := []*unicode.RangeTable{
|
|
unicode.L,
|
|
unicode.M,
|
|
unicode.N,
|
|
unicode.P,
|
|
unicode.S,
|
|
unicode.Z,
|
|
&spaceControlCharacters,
|
|
}
|
|
builder := strings.Builder{}
|
|
for _, roon := range s {
|
|
if unicode.IsOneOf(allowedRanges, roon) {
|
|
builder.WriteRune(roon) // returns nil error, per go doc
|
|
} else {
|
|
fmt.Fprintf(&builder, "%U", roon)
|
|
}
|
|
}
|
|
return builder.String()
|
|
}
|
|
|
|
// Namespace implements ClientConfig
|
|
func (config *DirectClientConfig) Namespace() (string, bool, error) {
|
|
if config.overrides != nil && config.overrides.Context.Namespace != "" {
|
|
// In the event we have an empty config but we do have a namespace override, we should return
|
|
// the namespace override instead of having config.ConfirmUsable() return an error. This allows
|
|
// things like in-cluster clients to execute `kubectl get pods --namespace=foo` and have the
|
|
// --namespace flag honored instead of being ignored.
|
|
return config.overrides.Context.Namespace, true, nil
|
|
}
|
|
|
|
if err := config.ConfirmUsable(); err != nil {
|
|
return "", false, err
|
|
}
|
|
|
|
configContext, err := config.getContext()
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
|
|
if len(configContext.Namespace) == 0 {
|
|
return "default", false, nil
|
|
}
|
|
|
|
return configContext.Namespace, false, nil
|
|
}
|
|
|
|
// ConfigAccess implements ClientConfig
|
|
func (config *DirectClientConfig) ConfigAccess() ConfigAccess {
|
|
return config.configAccess
|
|
}
|
|
|
|
// 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.
|
|
func (config *DirectClientConfig) ConfirmUsable() error {
|
|
validationErrors := make([]error, 0)
|
|
|
|
var contextName string
|
|
if len(config.contextName) != 0 {
|
|
contextName = config.contextName
|
|
} else {
|
|
contextName = config.config.CurrentContext
|
|
}
|
|
|
|
if len(contextName) > 0 {
|
|
_, exists := config.config.Contexts[contextName]
|
|
if !exists {
|
|
validationErrors = append(validationErrors, &errContextNotFound{contextName})
|
|
}
|
|
}
|
|
|
|
authInfoName, _ := config.getAuthInfoName()
|
|
authInfo, _ := config.getAuthInfo()
|
|
validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...)
|
|
clusterName, _ := config.getClusterName()
|
|
cluster, _ := config.getCluster()
|
|
validationErrors = append(validationErrors, validateClusterInfo(clusterName, cluster)...)
|
|
// 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})
|
|
}
|
|
return newErrConfigurationInvalid(validationErrors)
|
|
}
|
|
|
|
// getContextName returns the default, or user-set context name, and a boolean that indicates
|
|
// whether the default context name has been overwritten by a user-set flag, or left as its default value
|
|
func (config *DirectClientConfig) getContextName() (string, bool) {
|
|
if config.overrides != nil && len(config.overrides.CurrentContext) != 0 {
|
|
return config.overrides.CurrentContext, true
|
|
}
|
|
if len(config.contextName) != 0 {
|
|
return config.contextName, false
|
|
}
|
|
|
|
return config.config.CurrentContext, false
|
|
}
|
|
|
|
// getAuthInfoName returns a string containing the current authinfo name for the current context,
|
|
// and a boolean indicating whether the default authInfo name is overwritten by a user-set flag, or
|
|
// left as its default value
|
|
func (config *DirectClientConfig) getAuthInfoName() (string, bool) {
|
|
if config.overrides != nil && len(config.overrides.Context.AuthInfo) != 0 {
|
|
return config.overrides.Context.AuthInfo, true
|
|
}
|
|
context, _ := config.getContext()
|
|
return context.AuthInfo, false
|
|
}
|
|
|
|
// getClusterName returns a string containing the default, or user-set cluster name, and a boolean
|
|
// indicating whether the default clusterName has been overwritten by a user-set flag, or left as
|
|
// its default value
|
|
func (config *DirectClientConfig) getClusterName() (string, bool) {
|
|
if config.overrides != nil && len(config.overrides.Context.Cluster) != 0 {
|
|
return config.overrides.Context.Cluster, true
|
|
}
|
|
context, _ := config.getContext()
|
|
return context.Cluster, false
|
|
}
|
|
|
|
// getContext returns the clientcmdapi.Context, or an error if a required context is not found.
|
|
func (config *DirectClientConfig) getContext() (clientcmdapi.Context, error) {
|
|
contexts := config.config.Contexts
|
|
contextName, required := config.getContextName()
|
|
|
|
mergedContext := clientcmdapi.NewContext()
|
|
if configContext, exists := contexts[contextName]; exists {
|
|
mergo.Merge(mergedContext, configContext, mergo.WithOverride)
|
|
} else if required {
|
|
return clientcmdapi.Context{}, fmt.Errorf("context %q does not exist", contextName)
|
|
}
|
|
if config.overrides != nil {
|
|
mergo.Merge(mergedContext, config.overrides.Context, mergo.WithOverride)
|
|
}
|
|
|
|
return *mergedContext, nil
|
|
}
|
|
|
|
// getAuthInfo returns the clientcmdapi.AuthInfo, or an error if a required auth info is not found.
|
|
func (config *DirectClientConfig) getAuthInfo() (clientcmdapi.AuthInfo, error) {
|
|
authInfos := config.config.AuthInfos
|
|
authInfoName, required := config.getAuthInfoName()
|
|
|
|
mergedAuthInfo := clientcmdapi.NewAuthInfo()
|
|
if configAuthInfo, exists := authInfos[authInfoName]; exists {
|
|
mergo.Merge(mergedAuthInfo, configAuthInfo, mergo.WithOverride)
|
|
} else if required {
|
|
return clientcmdapi.AuthInfo{}, fmt.Errorf("auth info %q does not exist", authInfoName)
|
|
}
|
|
if config.overrides != nil {
|
|
mergo.Merge(mergedAuthInfo, config.overrides.AuthInfo, mergo.WithOverride)
|
|
}
|
|
|
|
return *mergedAuthInfo, nil
|
|
}
|
|
|
|
// getCluster returns the clientcmdapi.Cluster, or an error if a required cluster is not found.
|
|
func (config *DirectClientConfig) getCluster() (clientcmdapi.Cluster, error) {
|
|
clusterInfos := config.config.Clusters
|
|
clusterInfoName, required := config.getClusterName()
|
|
|
|
mergedClusterInfo := clientcmdapi.NewCluster()
|
|
if config.overrides != nil {
|
|
mergo.Merge(mergedClusterInfo, config.overrides.ClusterDefaults, mergo.WithOverride)
|
|
}
|
|
if configClusterInfo, exists := clusterInfos[clusterInfoName]; exists {
|
|
mergo.Merge(mergedClusterInfo, configClusterInfo, mergo.WithOverride)
|
|
} else if required {
|
|
return clientcmdapi.Cluster{}, fmt.Errorf("cluster %q does not exist", clusterInfoName)
|
|
}
|
|
if config.overrides != nil {
|
|
mergo.Merge(mergedClusterInfo, config.overrides.ClusterInfo, mergo.WithOverride)
|
|
}
|
|
|
|
// * 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".
|
|
// * An override of --certificate-authority should also override TLS skip settings and CA data, otherwise existing CA data will take precedence.
|
|
if config.overrides != nil {
|
|
caLen := len(config.overrides.ClusterInfo.CertificateAuthority)
|
|
caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData)
|
|
if config.overrides.ClusterInfo.InsecureSkipTLSVerify || caLen > 0 || caDataLen > 0 {
|
|
mergedClusterInfo.InsecureSkipTLSVerify = config.overrides.ClusterInfo.InsecureSkipTLSVerify
|
|
mergedClusterInfo.CertificateAuthority = config.overrides.ClusterInfo.CertificateAuthority
|
|
mergedClusterInfo.CertificateAuthorityData = config.overrides.ClusterInfo.CertificateAuthorityData
|
|
}
|
|
|
|
// if the --tls-server-name has been set in overrides, use that value.
|
|
// if the --server has been set in overrides, then use the value of --tls-server-name specified on the CLI too. This gives the property
|
|
// that setting a --server will effectively clear the KUBECONFIG value of tls-server-name if it is specified on the command line which is
|
|
// usually correct.
|
|
if config.overrides.ClusterInfo.TLSServerName != "" || config.overrides.ClusterInfo.Server != "" {
|
|
mergedClusterInfo.TLSServerName = config.overrides.ClusterInfo.TLSServerName
|
|
}
|
|
}
|
|
|
|
return *mergedClusterInfo, nil
|
|
}
|
|
|
|
// inClusterClientConfig makes a config that will work from within a kubernetes cluster container environment.
|
|
// Can take options overrides for flags explicitly provided to the command inside the cluster container.
|
|
type inClusterClientConfig struct {
|
|
overrides *ConfigOverrides
|
|
inClusterConfigProvider func() (*restclient.Config, error)
|
|
}
|
|
|
|
var _ ClientConfig = &inClusterClientConfig{}
|
|
|
|
func (config *inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
|
return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters")
|
|
}
|
|
|
|
func (config *inClusterClientConfig) ClientConfig() (*restclient.Config, error) {
|
|
inClusterConfigProvider := config.inClusterConfigProvider
|
|
if inClusterConfigProvider == nil {
|
|
inClusterConfigProvider = restclient.InClusterConfig
|
|
}
|
|
|
|
icc, err := inClusterConfigProvider()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// in-cluster configs only takes a host, token, or CA file
|
|
// if any of them were individually provided, overwrite anything else
|
|
if config.overrides != nil {
|
|
if server := config.overrides.ClusterInfo.Server; len(server) > 0 {
|
|
icc.Host = server
|
|
}
|
|
if len(config.overrides.AuthInfo.Token) > 0 || len(config.overrides.AuthInfo.TokenFile) > 0 {
|
|
icc.BearerToken = config.overrides.AuthInfo.Token
|
|
icc.BearerTokenFile = config.overrides.AuthInfo.TokenFile
|
|
}
|
|
if certificateAuthorityFile := config.overrides.ClusterInfo.CertificateAuthority; len(certificateAuthorityFile) > 0 {
|
|
icc.TLSClientConfig.CAFile = certificateAuthorityFile
|
|
}
|
|
}
|
|
|
|
return icc, nil
|
|
}
|
|
|
|
func (config *inClusterClientConfig) Namespace() (string, bool, error) {
|
|
// 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
|
|
if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
|
|
return ns, false, nil
|
|
}
|
|
|
|
// 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, false, nil
|
|
}
|
|
}
|
|
|
|
return "default", false, nil
|
|
}
|
|
|
|
func (config *inClusterClientConfig) ConfigAccess() ConfigAccess {
|
|
return NewDefaultClientConfigLoadingRules()
|
|
}
|
|
|
|
// Possible returns true if loading an inside-kubernetes-cluster is possible.
|
|
func (config *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()
|
|
}
|
|
|
|
// 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.
|
|
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
|
|
if kubeconfigPath == "" && masterUrl == "" {
|
|
klog.Warning("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.")
|
|
kubeconfig, err := restclient.InClusterConfig()
|
|
if err == nil {
|
|
return kubeconfig, nil
|
|
}
|
|
klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
|
|
}
|
|
return NewNonInteractiveDeferredLoadingClientConfig(
|
|
&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
|
|
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
|
|
}
|
|
|
|
// BuildConfigFromKubeconfigGetter is a helper function that builds configs from a master
|
|
// url and a kubeconfigGetter.
|
|
func BuildConfigFromKubeconfigGetter(masterUrl string, kubeconfigGetter KubeconfigGetter) (*restclient.Config, error) {
|
|
// TODO: We do not need a DeferredLoader here. Refactor code and see if we can use DirectClientConfig here.
|
|
cc := NewNonInteractiveDeferredLoadingClientConfig(
|
|
&ClientConfigGetter{kubeconfigGetter: kubeconfigGetter},
|
|
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}})
|
|
return cc.ClientConfig()
|
|
}
|