blob: 6e50eef51e780e4fb34eedc26549126e3f359ff2 [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package rest
18
19import (
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
44const (
45 DefaultQPS float32 = 5.0
46 DefaultBurst int = 10
47)
48
49var 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.
53type 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
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
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 WarningHandler WarningHandler
129
130 // The maximum length of time to wait before giving up on a server request. A value of zero means no timeout.
131 Timeout time.Duration
132
133 // Dial specifies the dial function for creating unencrypted TCP connections.
134 Dial func(ctx context.Context, network, address string) (net.Conn, error)
135
136 // Proxy is the the proxy func to be used for all requests made by this
137 // transport. If Proxy is nil, http.ProxyFromEnvironment is used. If Proxy
138 // returns a nil *URL, no proxy is used.
139 //
140 // socks5 proxying does not currently support spdy streaming endpoints.
141 Proxy func(*http.Request) (*url.URL, error)
142
143 // Version forces a specific version to be used (if registered)
144 // Do we need this?
145 // Version string
146}
147
148var _ fmt.Stringer = new(Config)
149var _ fmt.GoStringer = new(Config)
150
151type sanitizedConfig *Config
152
153type sanitizedAuthConfigPersister struct{ AuthProviderConfigPersister }
154
155func (sanitizedAuthConfigPersister) GoString() string {
156 return "rest.AuthProviderConfigPersister(--- REDACTED ---)"
157}
158func (sanitizedAuthConfigPersister) String() string {
159 return "rest.AuthProviderConfigPersister(--- REDACTED ---)"
160}
161
162// GoString implements fmt.GoStringer and sanitizes sensitive fields of Config
163// to prevent accidental leaking via logs.
164func (c *Config) GoString() string {
165 return c.String()
166}
167
168// String implements fmt.Stringer and sanitizes sensitive fields of Config to
169// prevent accidental leaking via logs.
170func (c *Config) String() string {
171 if c == nil {
172 return "<nil>"
173 }
174 cc := sanitizedConfig(CopyConfig(c))
175 // Explicitly mark non-empty credential fields as redacted.
176 if cc.Password != "" {
177 cc.Password = "--- REDACTED ---"
178 }
179 if cc.BearerToken != "" {
180 cc.BearerToken = "--- REDACTED ---"
181 }
182 if cc.AuthConfigPersister != nil {
183 cc.AuthConfigPersister = sanitizedAuthConfigPersister{cc.AuthConfigPersister}
184 }
185
186 return fmt.Sprintf("%#v", cc)
187}
188
189// ImpersonationConfig has all the available impersonation options
190type ImpersonationConfig struct {
191 // UserName is the username to impersonate on each request.
192 UserName string
193 // Groups are the groups to impersonate on each request.
194 Groups []string
195 // Extra is a free-form field which can be used to link some authentication information
196 // to authorization information. This field allows you to impersonate it.
197 Extra map[string][]string
198}
199
200// +k8s:deepcopy-gen=true
201// TLSClientConfig contains settings to enable transport layer security
202type TLSClientConfig struct {
203 // Server should be accessed without verifying the TLS certificate. For testing only.
204 Insecure bool
205 // ServerName is passed to the server for SNI and is used in the client to check server
206 // ceritificates against. If ServerName is empty, the hostname used to contact the
207 // server is used.
208 ServerName string
209
210 // Server requires TLS client certificate authentication
211 CertFile string
212 // Server requires TLS client certificate authentication
213 KeyFile string
214 // Trusted root certificates for server
215 CAFile string
216
217 // CertData holds PEM-encoded bytes (typically read from a client certificate file).
218 // CertData takes precedence over CertFile
219 CertData []byte
220 // KeyData holds PEM-encoded bytes (typically read from a client certificate key file).
221 // KeyData takes precedence over KeyFile
222 KeyData []byte
223 // CAData holds PEM-encoded bytes (typically read from a root certificates bundle).
224 // CAData takes precedence over CAFile
225 CAData []byte
226
227 // NextProtos is a list of supported application level protocols, in order of preference.
228 // Used to populate tls.Config.NextProtos.
229 // 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).
230 // To use only http/1.1, set to ["http/1.1"].
231 NextProtos []string
232}
233
234var _ fmt.Stringer = TLSClientConfig{}
235var _ fmt.GoStringer = TLSClientConfig{}
236
237type sanitizedTLSClientConfig TLSClientConfig
238
239// GoString implements fmt.GoStringer and sanitizes sensitive fields of
240// TLSClientConfig to prevent accidental leaking via logs.
241func (c TLSClientConfig) GoString() string {
242 return c.String()
243}
244
245// String implements fmt.Stringer and sanitizes sensitive fields of
246// TLSClientConfig to prevent accidental leaking via logs.
247func (c TLSClientConfig) String() string {
248 cc := sanitizedTLSClientConfig{
249 Insecure: c.Insecure,
250 ServerName: c.ServerName,
251 CertFile: c.CertFile,
252 KeyFile: c.KeyFile,
253 CAFile: c.CAFile,
254 CertData: c.CertData,
255 KeyData: c.KeyData,
256 CAData: c.CAData,
257 NextProtos: c.NextProtos,
258 }
259 // Explicitly mark non-empty credential fields as redacted.
260 if len(cc.CertData) != 0 {
261 cc.CertData = []byte("--- TRUNCATED ---")
262 }
263 if len(cc.KeyData) != 0 {
264 cc.KeyData = []byte("--- REDACTED ---")
265 }
266 return fmt.Sprintf("%#v", cc)
267}
268
269type ContentConfig struct {
270 // AcceptContentTypes specifies the types the client will accept and is optional.
271 // If not set, ContentType will be used to define the Accept header
272 AcceptContentTypes string
273 // ContentType specifies the wire format used to communicate with the server.
274 // This value will be set as the Accept header on requests made to the server, and
275 // as the default content type on any object sent to the server. If not set,
276 // "application/json" is used.
277 ContentType string
278 // GroupVersion is the API version to talk to. Must be provided when initializing
279 // a RESTClient directly. When initializing a Client, will be set with the default
280 // code version.
281 GroupVersion *schema.GroupVersion
282 // NegotiatedSerializer is used for obtaining encoders and decoders for multiple
283 // supported media types.
284 //
285 // TODO: NegotiatedSerializer will be phased out as internal clients are removed
286 // from Kubernetes.
287 NegotiatedSerializer runtime.NegotiatedSerializer
288}
289
290// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config
291// object. Note that a RESTClient may require fields that are optional when initializing a Client.
292// A RESTClient created by this method is generic - it expects to operate on an API that follows
293// the Kubernetes conventions, but may not be the Kubernetes API.
294func RESTClientFor(config *Config) (*RESTClient, error) {
295 if config.GroupVersion == nil {
296 return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient")
297 }
298 if config.NegotiatedSerializer == nil {
299 return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
300 }
301
302 baseURL, versionedAPIPath, err := defaultServerUrlFor(config)
303 if err != nil {
304 return nil, err
305 }
306
307 transport, err := TransportFor(config)
308 if err != nil {
309 return nil, err
310 }
311
312 var httpClient *http.Client
313 if transport != http.DefaultTransport {
314 httpClient = &http.Client{Transport: transport}
315 if config.Timeout > 0 {
316 httpClient.Timeout = config.Timeout
317 }
318 }
319
320 rateLimiter := config.RateLimiter
321 if rateLimiter == nil {
322 qps := config.QPS
323 if config.QPS == 0.0 {
324 qps = DefaultQPS
325 }
326 burst := config.Burst
327 if config.Burst == 0 {
328 burst = DefaultBurst
329 }
330 if qps > 0 {
331 rateLimiter = flowcontrol.NewTokenBucketRateLimiter(qps, burst)
332 }
333 }
334
335 var gv schema.GroupVersion
336 if config.GroupVersion != nil {
337 gv = *config.GroupVersion
338 }
339 clientContent := ClientContentConfig{
340 AcceptContentTypes: config.AcceptContentTypes,
341 ContentType: config.ContentType,
342 GroupVersion: gv,
343 Negotiator: runtime.NewClientNegotiator(config.NegotiatedSerializer, gv),
344 }
345
346 restClient, err := NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient)
347 if err == nil && config.WarningHandler != nil {
348 restClient.warningHandler = config.WarningHandler
349 }
350 return restClient, err
351}
352
353// UnversionedRESTClientFor is the same as RESTClientFor, except that it allows
354// the config.Version to be empty.
355func UnversionedRESTClientFor(config *Config) (*RESTClient, error) {
356 if config.NegotiatedSerializer == nil {
357 return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")
358 }
359
360 baseURL, versionedAPIPath, err := defaultServerUrlFor(config)
361 if err != nil {
362 return nil, err
363 }
364
365 transport, err := TransportFor(config)
366 if err != nil {
367 return nil, err
368 }
369
370 var httpClient *http.Client
371 if transport != http.DefaultTransport {
372 httpClient = &http.Client{Transport: transport}
373 if config.Timeout > 0 {
374 httpClient.Timeout = config.Timeout
375 }
376 }
377
378 rateLimiter := config.RateLimiter
379 if rateLimiter == nil {
380 qps := config.QPS
381 if config.QPS == 0.0 {
382 qps = DefaultQPS
383 }
384 burst := config.Burst
385 if config.Burst == 0 {
386 burst = DefaultBurst
387 }
388 if qps > 0 {
389 rateLimiter = flowcontrol.NewTokenBucketRateLimiter(qps, burst)
390 }
391 }
392
393 gv := metav1.SchemeGroupVersion
394 if config.GroupVersion != nil {
395 gv = *config.GroupVersion
396 }
397 clientContent := ClientContentConfig{
398 AcceptContentTypes: config.AcceptContentTypes,
399 ContentType: config.ContentType,
400 GroupVersion: gv,
401 Negotiator: runtime.NewClientNegotiator(config.NegotiatedSerializer, gv),
402 }
403
404 restClient, err := NewRESTClient(baseURL, versionedAPIPath, clientContent, rateLimiter, httpClient)
405 if err == nil && config.WarningHandler != nil {
406 restClient.warningHandler = config.WarningHandler
407 }
408 return restClient, err
409}
410
411// SetKubernetesDefaults sets default values on the provided client config for accessing the
412// Kubernetes API or returns an error if any of the defaults are impossible or invalid.
413func SetKubernetesDefaults(config *Config) error {
414 if len(config.UserAgent) == 0 {
415 config.UserAgent = DefaultKubernetesUserAgent()
416 }
417 return nil
418}
419
420// adjustCommit returns sufficient significant figures of the commit's git hash.
421func adjustCommit(c string) string {
422 if len(c) == 0 {
423 return "unknown"
424 }
425 if len(c) > 7 {
426 return c[:7]
427 }
428 return c
429}
430
431// adjustVersion strips "alpha", "beta", etc. from version in form
432// major.minor.patch-[alpha|beta|etc].
433func adjustVersion(v string) string {
434 if len(v) == 0 {
435 return "unknown"
436 }
437 seg := strings.SplitN(v, "-", 2)
438 return seg[0]
439}
440
441// adjustCommand returns the last component of the
442// OS-specific command path for use in User-Agent.
443func adjustCommand(p string) string {
444 // Unlikely, but better than returning "".
445 if len(p) == 0 {
446 return "unknown"
447 }
448 return filepath.Base(p)
449}
450
451// buildUserAgent builds a User-Agent string from given args.
452func buildUserAgent(command, version, os, arch, commit string) string {
453 return fmt.Sprintf(
454 "%s/%s (%s/%s) kubernetes/%s", command, version, os, arch, commit)
455}
456
457// DefaultKubernetesUserAgent returns a User-Agent string built from static global vars.
458func DefaultKubernetesUserAgent() string {
459 return buildUserAgent(
460 adjustCommand(os.Args[0]),
461 adjustVersion(version.Get().GitVersion),
462 gruntime.GOOS,
463 gruntime.GOARCH,
464 adjustCommit(version.Get().GitCommit))
465}
466
467// InClusterConfig returns a config object which uses the service account
468// kubernetes gives to pods. It's intended for clients that expect to be
469// running inside a pod running on kubernetes. It will return ErrNotInCluster
470// if called from a process not running in a kubernetes environment.
471func InClusterConfig() (*Config, error) {
472 const (
473 tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
474 rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
475 )
476 host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
477 if len(host) == 0 || len(port) == 0 {
478 return nil, ErrNotInCluster
479 }
480
481 token, err := ioutil.ReadFile(tokenFile)
482 if err != nil {
483 return nil, err
484 }
485
486 tlsClientConfig := TLSClientConfig{}
487
488 if _, err := certutil.NewPool(rootCAFile); err != nil {
489 klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
490 } else {
491 tlsClientConfig.CAFile = rootCAFile
492 }
493
494 return &Config{
495 // TODO: switch to using cluster DNS.
496 Host: "https://" + net.JoinHostPort(host, port),
497 TLSClientConfig: tlsClientConfig,
498 BearerToken: string(token),
499 BearerTokenFile: tokenFile,
500 }, nil
501}
502
503// IsConfigTransportTLS returns true if and only if the provided
504// config will result in a protected connection to the server when it
505// is passed to restclient.RESTClientFor(). Use to determine when to
506// send credentials over the wire.
507//
508// Note: the Insecure flag is ignored when testing for this value, so MITM attacks are
509// still possible.
510func IsConfigTransportTLS(config Config) bool {
511 baseURL, _, err := defaultServerUrlFor(&config)
512 if err != nil {
513 return false
514 }
515 return baseURL.Scheme == "https"
516}
517
518// LoadTLSFiles copies the data from the CertFile, KeyFile, and CAFile fields into the CertData,
519// KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are
520// either populated or were empty to start.
521func LoadTLSFiles(c *Config) error {
522 var err error
523 c.CAData, err = dataFromSliceOrFile(c.CAData, c.CAFile)
524 if err != nil {
525 return err
526 }
527
528 c.CertData, err = dataFromSliceOrFile(c.CertData, c.CertFile)
529 if err != nil {
530 return err
531 }
532
533 c.KeyData, err = dataFromSliceOrFile(c.KeyData, c.KeyFile)
534 if err != nil {
535 return err
536 }
537 return nil
538}
539
540// dataFromSliceOrFile returns data from the slice (if non-empty), or from the file,
541// or an error if an error occurred reading the file
542func dataFromSliceOrFile(data []byte, file string) ([]byte, error) {
543 if len(data) > 0 {
544 return data, nil
545 }
546 if len(file) > 0 {
547 fileData, err := ioutil.ReadFile(file)
548 if err != nil {
549 return []byte{}, err
550 }
551 return fileData, nil
552 }
553 return nil, nil
554}
555
556func AddUserAgent(config *Config, userAgent string) *Config {
557 fullUserAgent := DefaultKubernetesUserAgent() + "/" + userAgent
558 config.UserAgent = fullUserAgent
559 return config
560}
561
562// 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
563func AnonymousClientConfig(config *Config) *Config {
564 // copy only known safe fields
565 return &Config{
566 Host: config.Host,
567 APIPath: config.APIPath,
568 ContentConfig: config.ContentConfig,
569 TLSClientConfig: TLSClientConfig{
570 Insecure: config.Insecure,
571 ServerName: config.ServerName,
572 CAFile: config.TLSClientConfig.CAFile,
573 CAData: config.TLSClientConfig.CAData,
574 NextProtos: config.TLSClientConfig.NextProtos,
575 },
576 RateLimiter: config.RateLimiter,
577 WarningHandler: config.WarningHandler,
578 UserAgent: config.UserAgent,
579 DisableCompression: config.DisableCompression,
580 QPS: config.QPS,
581 Burst: config.Burst,
582 Timeout: config.Timeout,
583 Dial: config.Dial,
584 Proxy: config.Proxy,
585 }
586}
587
588// CopyConfig returns a copy of the given config
589func CopyConfig(config *Config) *Config {
590 return &Config{
591 Host: config.Host,
592 APIPath: config.APIPath,
593 ContentConfig: config.ContentConfig,
594 Username: config.Username,
595 Password: config.Password,
596 BearerToken: config.BearerToken,
597 BearerTokenFile: config.BearerTokenFile,
598 Impersonate: ImpersonationConfig{
599 Groups: config.Impersonate.Groups,
600 Extra: config.Impersonate.Extra,
601 UserName: config.Impersonate.UserName,
602 },
603 AuthProvider: config.AuthProvider,
604 AuthConfigPersister: config.AuthConfigPersister,
605 ExecProvider: config.ExecProvider,
606 TLSClientConfig: TLSClientConfig{
607 Insecure: config.TLSClientConfig.Insecure,
608 ServerName: config.TLSClientConfig.ServerName,
609 CertFile: config.TLSClientConfig.CertFile,
610 KeyFile: config.TLSClientConfig.KeyFile,
611 CAFile: config.TLSClientConfig.CAFile,
612 CertData: config.TLSClientConfig.CertData,
613 KeyData: config.TLSClientConfig.KeyData,
614 CAData: config.TLSClientConfig.CAData,
615 NextProtos: config.TLSClientConfig.NextProtos,
616 },
617 UserAgent: config.UserAgent,
618 DisableCompression: config.DisableCompression,
619 Transport: config.Transport,
620 WrapTransport: config.WrapTransport,
621 QPS: config.QPS,
622 Burst: config.Burst,
623 RateLimiter: config.RateLimiter,
624 WarningHandler: config.WarningHandler,
625 Timeout: config.Timeout,
626 Dial: config.Dial,
627 Proxy: config.Proxy,
628 }
629}