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