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