k8s.io/client-go@v0.22.2/rest/config.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     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
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    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.
    15  */
    16  
    17  package rest
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"net"
    25  	"net/http"
    26  	"net/url"
    27  	"os"
    28  	"path/filepath"
    29  	gruntime "runtime"
    30  	"strings"
    31  	"time"
    32  
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/client-go/pkg/version"
    37  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    38  	"k8s.io/client-go/transport"
    39  	certutil "k8s.io/client-go/util/cert"
    40  	"k8s.io/client-go/util/flowcontrol"
    41  	"k8s.io/klog/v2"
    42  )
    43  
    44  const (
    45  	DefaultQPS   float32 = 5.0
    46  	DefaultBurst int     = 10
    47  )
    48  
    49  var ErrNotInCluster = errors.New("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined")
    50  
    51  // Config holds the common attributes that can be passed to a Kubernetes client on
    52  // initialization.
    53  type Config struct {
    54  	// Host must be a host string, a host:port pair, or a URL to the base of the apiserver.
    55  	// If a URL is given then the (optional) Path of that URL represents a prefix that must
    56  	// be appended to all request URIs used to access the apiserver. This allows a frontend
    57  	// proxy to easily relocate all of the apiserver endpoints.
    58  	Host string
    59  	// APIPath is a sub-path that points to an API root.
    60  	APIPath string
    61  
    62  	// ContentConfig contains settings that affect how objects are transformed when
    63  	// sent to the server.
    64  	ContentConfig
    65  
    66  	// Server requires Basic authentication
    67  	Username string
    68  	Password string `datapolicy:"password"`
    69  
    70  	// Server requires Bearer authentication. This client will not attempt to use
    71  	// refresh tokens for an OAuth2 flow.
    72  	// TODO: demonstrate an OAuth2 compatible client.
    73  	BearerToken string `datapolicy:"token"`
    74  
    75  	// Path to a file containing a BearerToken.
    76  	// If set, the contents are periodically read.
    77  	// The last successfully read value takes precedence over BearerToken.
    78  	BearerTokenFile string
    79  
    80  	// Impersonate is the configuration that RESTClient will use for impersonation.
    81  	Impersonate ImpersonationConfig
    82  
    83  	// Server requires plugin-specified authentication.
    84  	AuthProvider *clientcmdapi.AuthProviderConfig
    85  
    86  	// Callback to persist config for AuthProvider.
    87  	AuthConfigPersister AuthProviderConfigPersister
    88  
    89  	// Exec-based authentication provider.
    90  	ExecProvider *clientcmdapi.ExecConfig
    91  
    92  	// TLSClientConfig contains settings to enable transport layer security
    93  	TLSClientConfig
    94  
    95  	// UserAgent is an optional field that specifies the caller of this request.
    96  	UserAgent string
    97  
    98  	// DisableCompression bypasses automatic GZip compression requests to the
    99  	// server.
   100  	DisableCompression bool
   101  
   102  	// Transport may be used for custom HTTP behavior. This attribute may not
   103  	// be specified with the TLS client certificate options. Use WrapTransport
   104  	// to provide additional per-server middleware behavior.
   105  	Transport http.RoundTripper
   106  	// WrapTransport will be invoked for custom HTTP behavior after the underlying
   107  	// transport is initialized (either the transport created from TLSClientConfig,
   108  	// Transport, or http.DefaultTransport). The config may layer other RoundTrippers
   109  	// on top of the returned RoundTripper.
   110  	//
   111  	// A future release will change this field to an array. Use config.Wrap()
   112  	// instead of setting this value directly.
   113  	WrapTransport transport.WrapperFunc
   114  
   115  	// QPS indicates the maximum QPS to the master from this client.
   116  	// If it's zero, the created RESTClient will use DefaultQPS: 5
   117  	QPS float32
   118  
   119  	// Maximum burst for throttle.
   120  	// If it's zero, the created RESTClient will use DefaultBurst: 10.
   121  	Burst int
   122  
   123  	// Rate limiter for limiting connections to the master from this client. If present overwrites QPS/Burst
   124  	RateLimiter flowcontrol.RateLimiter
   125  
   126  	// WarningHandler handles warnings in server responses.
   127  	// If not set, the default warning handler is used.
   128  	// See documentation for SetDefaultWarningHandler() for details.
   129  	WarningHandler WarningHandler
   130  
   131  	// The maximum length of time to wait before giving up on a server request. A value of zero means no timeout.
   132  	Timeout time.Duration
   133  
   134  	// Dial specifies the dial function for creating unencrypted TCP connections.
   135  	Dial func(ctx context.Context, network, address string) (net.Conn, error)
   136  
   137  	// Proxy is the proxy func to be used for all requests made by this
   138  	// transport. If Proxy is nil, http.ProxyFromEnvironment is used. If Proxy
   139  	// returns a nil *URL, no proxy is used.
   140  	//
   141  	// socks5 proxying does not currently support spdy streaming endpoints.
   142  	Proxy func(*http.Request) (*url.URL, error)
   143  
   144  	// Version forces a specific version to be used (if registered)
   145  	// Do we need this?
   146  	// Version string
   147  }
   148  
   149  var _ fmt.Stringer = new(Config)
   150  var _ fmt.GoStringer = new(Config)
   151  
   152  type sanitizedConfig *Config
   153  
   154  type sanitizedAuthConfigPersister struct{ AuthProviderConfigPersister }
   155  
   156  func (sanitizedAuthConfigPersister) GoString() string {
   157  	return "rest.AuthProviderConfigPersister(--- REDACTED ---)"
   158  }
   159  func (sanitizedAuthConfigPersister) String() string {
   160  	return "rest.AuthProviderConfigPersister(--- REDACTED ---)"
   161  }
   162  
   163  type sanitizedObject struct{ runtime.Object }
   164  
   165  func (sanitizedObject) GoString() string {
   166  	return "runtime.Object(--- REDACTED ---)"
   167  }
   168  func (sanitizedObject) String() string {
   169  	return "runtime.Object(--- REDACTED ---)"
   170  }
   171  
   172  // GoString implements fmt.GoStringer and sanitizes sensitive fields of Config
   173  // to prevent accidental leaking via logs.
   174  func (c *Config) GoString() string {
   175  	return c.String()
   176  }
   177  
   178  // String implements fmt.Stringer and sanitizes sensitive fields of Config to
   179  // prevent accidental leaking via logs.
   180  func (c *Config) String() string {
   181  	if c == nil {
   182  		return "<nil>"
   183  	}
   184  	cc := sanitizedConfig(CopyConfig(c))
   185  	// Explicitly mark non-empty credential fields as redacted.
   186  	if cc.Password != "" {
   187  		cc.Password = "--- REDACTED ---"
   188  	}
   189  	if cc.BearerToken != "" {
   190  		cc.BearerToken = "--- REDACTED ---"
   191  	}
   192  	if cc.AuthConfigPersister != nil {
   193  		cc.AuthConfigPersister = sanitizedAuthConfigPersister{cc.AuthConfigPersister}
   194  	}
   195  	if cc.ExecProvider != nil && cc.ExecProvider.Config != nil {
   196  		cc.ExecProvider.Config = sanitizedObject{Object: cc.ExecProvider.Config}
   197  	}
   198  	return fmt.Sprintf("%#v", cc)
   199  }
   200  
   201  // ImpersonationConfig has all the available impersonation options
   202  type ImpersonationConfig struct {
   203  	// UserName is the username to impersonate on each request.
   204  	UserName string
   205  	// Groups are the groups to impersonate on each request.
   206  	Groups []string
   207  	// Extra is a free-form field which can be used to link some authentication information
   208  	// to authorization information.  This field allows you to impersonate it.
   209  	Extra map[string][]string
   210  }
   211  
   212  // +k8s:deepcopy-gen=true
   213  // TLSClientConfig contains settings to enable transport layer security
   214  type TLSClientConfig struct {
   215  	// Server should be accessed without verifying the TLS certificate. For testing only.
   216  	Insecure bool
   217  	// ServerName is passed to the server for SNI and is used in the client to check server
   218  	// certificates against. If ServerName is empty, the hostname used to contact the
   219  	// server is used.
   220  	ServerName string
   221  
   222  	// Server requires TLS client certificate authentication
   223  	CertFile string
   224  	// Server requires TLS client certificate authentication
   225  	KeyFile string
   226  	// Trusted root certificates for server
   227  	CAFile string
   228  
   229  	// CertData holds PEM-encoded bytes (typically read from a client certificate file).
   230  	// CertData takes precedence over CertFile
   231  	CertData []byte
   232  	// KeyData holds PEM-encoded bytes (typically read from a client certificate key file).
   233  	// KeyData takes precedence over KeyFile
   234  	KeyData []byte `datapolicy:"security-key"`
   235  	// CAData holds PEM-encoded bytes (typically read from a root certificates bundle).
   236  	// CAData takes precedence over CAFile
   237  	CAData []byte
   238  
   239  	// NextProtos is a list of supported application level protocols, in order of preference.
   240  	// Used to populate tls.Config.NextProtos.
   241  	// To indicate to the server http/1.1 is preferred over http/2, set to ["http/1.1", "h2"] (though the server is free to ignore that preference).
   242  	// To use only http/1.1, set to ["http/1.1"].
   243  	NextProtos []string
   244  }
   245  
   246  var _ fmt.Stringer = TLSClientConfig{}
   247  var _ fmt.GoStringer = TLSClientConfig{}
   248  
   249  type sanitizedTLSClientConfig TLSClientConfig
   250  
   251  // GoString implements fmt.GoStringer and sanitizes sensitive fields of
   252  // TLSClientConfig to prevent accidental leaking via logs.
   253  func (c TLSClientConfig) GoString() string {
   254  	return c.String()
   255  }
   256  
   257  // String implements fmt.Stringer and sanitizes sensitive fields of
   258  // TLSClientConfig to prevent accidental leaking via logs.
   259  func (c TLSClientConfig) String() string {
   260  	cc := sanitizedTLSClientConfig{
   261  		Insecure:   c.Insecure,
   262  		ServerName: c.ServerName,
   263  		CertFile:   c.CertFile,
   264  		KeyFile:    c.KeyFile,
   265  		CAFile:     c.CAFile,
   266  		CertData:   c.CertData,
   267  		KeyData:    c.KeyData,
   268  		CAData:     c.CAData,
   269  		NextProtos: c.NextProtos,
   270  	}
   271  	// Explicitly mark non-empty credential fields as redacted.
   272  	if len(cc.CertData) != 0 {
   273  		cc.CertData = []byte("--- TRUNCATED ---")
   274  	}
   275  	if len(cc.KeyData) != 0 {
   276  		cc.KeyData = []byte("--- REDACTED ---")
   277  	}
   278  	return fmt.Sprintf("%#v", cc)
   279  }
   280  
   281  type ContentConfig struct {
   282  	// AcceptContentTypes specifies the types the client will accept and is optional.
   283  	// If not set, ContentType will be used to define the Accept header
   284  	AcceptContentTypes string
   285  	// ContentType specifies the wire format used to communicate with the server.
   286  	// This value will be set as the Accept header on requests made to the server, and
   287  	// as the default content type on any object sent to the server. If not set,
   288  	// "application/json" is used.
   289  	ContentType string
   290  	// GroupVersion is the API version to talk to. Must be provided when initializing
   291  	// a RESTClient directly. When initializing a Client, will be set with the default
   292  	// code version.
   293  	GroupVersion *schema.GroupVersion
   294  	// NegotiatedSerializer is used for obtaining encoders and decoders for multiple
   295  	// supported media types.
   296  	//
   297  	// TODO: NegotiatedSerializer will be phased out as internal clients are removed
   298  	//   from Kubernetes.
   299  	NegotiatedSerializer runtime.NegotiatedSerializer
   300  }
   301  
   302  // RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config
   303  // object. Note that a RESTClient may require fields that are optional when initializing a Client.
   304  // A RESTClient created by this method is generic - it expects to operate on an API that follows
   305  // the Kubernetes conventions, but may not be the Kubernetes API.
   306  func RESTClientFor(config *Config) (*RESTClient, error) {
   307  	if config.GroupVersion == nil {
   308  		return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient")
   309  	}
   310  	if config.NegotiatedSerializer == nil {
   311  		return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
   312  	}
   313  
   314  	baseURL, versionedAPIPath, err := defaultServerUrlFor(config)
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  
   319  	transport, err := TransportFor(config)
   320  	if err != nil {
   321  		return nil, err
   322  	}
   323  
   324  	var httpClient *http.Client
   325  	if transport != http.DefaultTransport {
   326  		httpClient = &http.Client{Transport: transport}
   327  		if config.Timeout > 0 {
   328  			httpClient.Timeout = config.Timeout
   329  		}
   330  	}
   331  
   332  	rateLimiter := config.RateLimiter
   333  	if rateLimiter == nil {
   334  		qps := config.QPS
   335  		if config.QPS == 0.0 {
   336  			qps = DefaultQPS
   337  		}
   338  		burst := config.Burst
   339  		if config.Burst == 0 {
   340  			burst = DefaultBurst
   341  		}
   342  		if qps > 0 {
   343  			rateLimiter = flowcontrol.NewTokenBucketRateLimiter(qps, burst)
   344  		}
   345  	}
   346  
   347  	var gv schema.GroupVersion
   348  	if config.GroupVersion != nil {
   349  		gv = *config.GroupVersion
   350  	}
   351  	clientContent := ClientContentConfig{
   352  		AcceptContentTypes: config.AcceptContentTypes,
   353  		ContentType:        config.ContentType,
   354  		GroupVersion:       gv,
   355  		Negotiator:         runtime.NewClientNegotiator(config.NegotiatedSerializer, gv),
   356  	}
   357  
   358  	restClient, err := NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient)
   359  	if err == nil && config.WarningHandler != nil {
   360  		restClient.warningHandler = config.WarningHandler
   361  	}
   362  	return restClient, err
   363  }
   364  
   365  // UnversionedRESTClientFor is the same as RESTClientFor, except that it allows
   366  // the config.Version to be empty.
   367  func UnversionedRESTClientFor(config *Config) (*RESTClient, error) {
   368  	if config.NegotiatedSerializer == nil {
   369  		return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
   370  	}
   371  
   372  	baseURL, versionedAPIPath, err := defaultServerUrlFor(config)
   373  	if err != nil {
   374  		return nil, err
   375  	}
   376  
   377  	transport, err := TransportFor(config)
   378  	if err != nil {
   379  		return nil, err
   380  	}
   381  
   382  	var httpClient *http.Client
   383  	if transport != http.DefaultTransport {
   384  		httpClient = &http.Client{Transport: transport}
   385  		if config.Timeout > 0 {
   386  			httpClient.Timeout = config.Timeout
   387  		}
   388  	}
   389  
   390  	rateLimiter := config.RateLimiter
   391  	if rateLimiter == nil {
   392  		qps := config.QPS
   393  		if config.QPS == 0.0 {
   394  			qps = DefaultQPS
   395  		}
   396  		burst := config.Burst
   397  		if config.Burst == 0 {
   398  			burst = DefaultBurst
   399  		}
   400  		if qps > 0 {
   401  			rateLimiter = flowcontrol.NewTokenBucketRateLimiter(qps, burst)
   402  		}
   403  	}
   404  
   405  	gv := metav1.SchemeGroupVersion
   406  	if config.GroupVersion != nil {
   407  		gv = *config.GroupVersion
   408  	}
   409  	clientContent := ClientContentConfig{
   410  		AcceptContentTypes: config.AcceptContentTypes,
   411  		ContentType:        config.ContentType,
   412  		GroupVersion:       gv,
   413  		Negotiator:         runtime.NewClientNegotiator(config.NegotiatedSerializer, gv),
   414  	}
   415  
   416  	restClient, err := NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient)
   417  	if err == nil && config.WarningHandler != nil {
   418  		restClient.warningHandler = config.WarningHandler
   419  	}
   420  	return restClient, err
   421  }
   422  
   423  // SetKubernetesDefaults sets default values on the provided client config for accessing the
   424  // Kubernetes API or returns an error if any of the defaults are impossible or invalid.
   425  func SetKubernetesDefaults(config *Config) error {
   426  	if len(config.UserAgent) == 0 {
   427  		config.UserAgent = DefaultKubernetesUserAgent()
   428  	}
   429  	return nil
   430  }
   431  
   432  // adjustCommit returns sufficient significant figures of the commit's git hash.
   433  func adjustCommit(c string) string {
   434  	if len(c) == 0 {
   435  		return "unknown"
   436  	}
   437  	if len(c) > 7 {
   438  		return c[:7]
   439  	}
   440  	return c
   441  }
   442  
   443  // adjustVersion strips "alpha", "beta", etc. from version in form
   444  // major.minor.patch-[alpha|beta|etc].
   445  func adjustVersion(v string) string {
   446  	if len(v) == 0 {
   447  		return "unknown"
   448  	}
   449  	seg := strings.SplitN(v, "-", 2)
   450  	return seg[0]
   451  }
   452  
   453  // adjustCommand returns the last component of the
   454  // OS-specific command path for use in User-Agent.
   455  func adjustCommand(p string) string {
   456  	// Unlikely, but better than returning "".
   457  	if len(p) == 0 {
   458  		return "unknown"
   459  	}
   460  	return filepath.Base(p)
   461  }
   462  
   463  // buildUserAgent builds a User-Agent string from given args.
   464  func buildUserAgent(command, version, os, arch, commit string) string {
   465  	return fmt.Sprintf(
   466  		"%s/%s (%s/%s) kubernetes/%s", command, version, os, arch, commit)
   467  }
   468  
   469  // DefaultKubernetesUserAgent returns a User-Agent string built from static global vars.
   470  func DefaultKubernetesUserAgent() string {
   471  	return buildUserAgent(
   472  		adjustCommand(os.Args[0]),
   473  		adjustVersion(version.Get().GitVersion),
   474  		gruntime.GOOS,
   475  		gruntime.GOARCH,
   476  		adjustCommit(version.Get().GitCommit))
   477  }
   478  
   479  // InClusterConfig returns a config object which uses the service account
   480  // kubernetes gives to pods. It's intended for clients that expect to be
   481  // running inside a pod running on kubernetes. It will return ErrNotInCluster
   482  // if called from a process not running in a kubernetes environment.
   483  func InClusterConfig() (*Config, error) {
   484  	const (
   485  		tokenFile  = "/var/run/secrets/kubernetes.io/serviceaccount/token"
   486  		rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
   487  	)
   488  	host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
   489  	if len(host) == 0 || len(port) == 0 {
   490  		return nil, ErrNotInCluster
   491  	}
   492  
   493  	token, err := ioutil.ReadFile(tokenFile)
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  
   498  	tlsClientConfig := TLSClientConfig{}
   499  
   500  	if _, err := certutil.NewPool(rootCAFile); err != nil {
   501  		klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
   502  	} else {
   503  		tlsClientConfig.CAFile = rootCAFile
   504  	}
   505  
   506  	return &Config{
   507  		// TODO: switch to using cluster DNS.
   508  		Host:            "https://" + net.JoinHostPort(host, port),
   509  		TLSClientConfig: tlsClientConfig,
   510  		BearerToken:     string(token),
   511  		BearerTokenFile: tokenFile,
   512  	}, nil
   513  }
   514  
   515  // IsConfigTransportTLS returns true if and only if the provided
   516  // config will result in a protected connection to the server when it
   517  // is passed to restclient.RESTClientFor().  Use to determine when to
   518  // send credentials over the wire.
   519  //
   520  // Note: the Insecure flag is ignored when testing for this value, so MITM attacks are
   521  // still possible.
   522  func IsConfigTransportTLS(config Config) bool {
   523  	baseURL, _, err := defaultServerUrlFor(&config)
   524  	if err != nil {
   525  		return false
   526  	}
   527  	return baseURL.Scheme == "https"
   528  }
   529  
   530  // LoadTLSFiles copies the data from the CertFile, KeyFile, and CAFile fields into the CertData,
   531  // KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are
   532  // either populated or were empty to start.
   533  func LoadTLSFiles(c *Config) error {
   534  	var err error
   535  	c.CAData, err = dataFromSliceOrFile(c.CAData, c.CAFile)
   536  	if err != nil {
   537  		return err
   538  	}
   539  
   540  	c.CertData, err = dataFromSliceOrFile(c.CertData, c.CertFile)
   541  	if err != nil {
   542  		return err
   543  	}
   544  
   545  	c.KeyData, err = dataFromSliceOrFile(c.KeyData, c.KeyFile)
   546  	if err != nil {
   547  		return err
   548  	}
   549  	return nil
   550  }
   551  
   552  // dataFromSliceOrFile returns data from the slice (if non-empty), or from the file,
   553  // or an error if an error occurred reading the file
   554  func dataFromSliceOrFile(data []byte, file string) ([]byte, error) {
   555  	if len(data) > 0 {
   556  		return data, nil
   557  	}
   558  	if len(file) > 0 {
   559  		fileData, err := ioutil.ReadFile(file)
   560  		if err != nil {
   561  			return []byte{}, err
   562  		}
   563  		return fileData, nil
   564  	}
   565  	return nil, nil
   566  }
   567  
   568  func AddUserAgent(config *Config, userAgent string) *Config {
   569  	fullUserAgent := DefaultKubernetesUserAgent() + "/" + userAgent
   570  	config.UserAgent = fullUserAgent
   571  	return config
   572  }
   573  
   574  // AnonymousClientConfig returns a copy of the given config with all user credentials (cert/key, bearer token, and username/password) and custom transports (WrapTransport, Transport) removed
   575  func AnonymousClientConfig(config *Config) *Config {
   576  	// copy only known safe fields
   577  	return &Config{
   578  		Host:          config.Host,
   579  		APIPath:       config.APIPath,
   580  		ContentConfig: config.ContentConfig,
   581  		TLSClientConfig: TLSClientConfig{
   582  			Insecure:   config.Insecure,
   583  			ServerName: config.ServerName,
   584  			CAFile:     config.TLSClientConfig.CAFile,
   585  			CAData:     config.TLSClientConfig.CAData,
   586  			NextProtos: config.TLSClientConfig.NextProtos,
   587  		},
   588  		RateLimiter:        config.RateLimiter,
   589  		WarningHandler:     config.WarningHandler,
   590  		UserAgent:          config.UserAgent,
   591  		DisableCompression: config.DisableCompression,
   592  		QPS:                config.QPS,
   593  		Burst:              config.Burst,
   594  		Timeout:            config.Timeout,
   595  		Dial:               config.Dial,
   596  		Proxy:              config.Proxy,
   597  	}
   598  }
   599  
   600  // CopyConfig returns a copy of the given config
   601  func CopyConfig(config *Config) *Config {
   602  	c := &Config{
   603  		Host:            config.Host,
   604  		APIPath:         config.APIPath,
   605  		ContentConfig:   config.ContentConfig,
   606  		Username:        config.Username,
   607  		Password:        config.Password,
   608  		BearerToken:     config.BearerToken,
   609  		BearerTokenFile: config.BearerTokenFile,
   610  		Impersonate: ImpersonationConfig{
   611  			Groups:   config.Impersonate.Groups,
   612  			Extra:    config.Impersonate.Extra,
   613  			UserName: config.Impersonate.UserName,
   614  		},
   615  		AuthProvider:        config.AuthProvider,
   616  		AuthConfigPersister: config.AuthConfigPersister,
   617  		ExecProvider:        config.ExecProvider,
   618  		TLSClientConfig: TLSClientConfig{
   619  			Insecure:   config.TLSClientConfig.Insecure,
   620  			ServerName: config.TLSClientConfig.ServerName,
   621  			CertFile:   config.TLSClientConfig.CertFile,
   622  			KeyFile:    config.TLSClientConfig.KeyFile,
   623  			CAFile:     config.TLSClientConfig.CAFile,
   624  			CertData:   config.TLSClientConfig.CertData,
   625  			KeyData:    config.TLSClientConfig.KeyData,
   626  			CAData:     config.TLSClientConfig.CAData,
   627  			NextProtos: config.TLSClientConfig.NextProtos,
   628  		},
   629  		UserAgent:          config.UserAgent,
   630  		DisableCompression: config.DisableCompression,
   631  		Transport:          config.Transport,
   632  		WrapTransport:      config.WrapTransport,
   633  		QPS:                config.QPS,
   634  		Burst:              config.Burst,
   635  		RateLimiter:        config.RateLimiter,
   636  		WarningHandler:     config.WarningHandler,
   637  		Timeout:            config.Timeout,
   638  		Dial:               config.Dial,
   639  		Proxy:              config.Proxy,
   640  	}
   641  	if config.ExecProvider != nil && config.ExecProvider.Config != nil {
   642  		c.ExecProvider.Config = config.ExecProvider.Config.DeepCopyObject()
   643  	}
   644  	return c
   645  }