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 }