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