1
16
17 package rest
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "io"
24 "net"
25 "net/http"
26 "net/url"
27 "path/filepath"
28 "reflect"
29 "strings"
30 "testing"
31 "time"
32
33 v1 "k8s.io/api/core/v1"
34 "k8s.io/apimachinery/pkg/runtime"
35 "k8s.io/apimachinery/pkg/runtime/schema"
36 "k8s.io/client-go/kubernetes/scheme"
37 clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
38 "k8s.io/client-go/transport"
39 "k8s.io/client-go/util/flowcontrol"
40
41 "github.com/google/go-cmp/cmp"
42 fuzz "github.com/google/gofuzz"
43 "github.com/stretchr/testify/assert"
44 )
45
46 func TestIsConfigTransportTLS(t *testing.T) {
47 testCases := []struct {
48 Config *Config
49 TransportTLS bool
50 }{
51 {
52 Config: &Config{},
53 TransportTLS: false,
54 },
55 {
56 Config: &Config{
57 Host: "https://localhost",
58 },
59 TransportTLS: true,
60 },
61 {
62 Config: &Config{
63 Host: "localhost",
64 TLSClientConfig: TLSClientConfig{
65 CertFile: "foo",
66 },
67 },
68 TransportTLS: true,
69 },
70 {
71 Config: &Config{
72 Host: "///:://localhost",
73 TLSClientConfig: TLSClientConfig{
74 CertFile: "foo",
75 },
76 },
77 TransportTLS: false,
78 },
79 {
80 Config: &Config{
81 Host: "1.2.3.4:567",
82 TLSClientConfig: TLSClientConfig{
83 Insecure: true,
84 },
85 },
86 TransportTLS: true,
87 },
88 }
89 for _, testCase := range testCases {
90 if err := SetKubernetesDefaults(testCase.Config); err != nil {
91 t.Errorf("setting defaults failed for %#v: %v", testCase.Config, err)
92 continue
93 }
94 useTLS := IsConfigTransportTLS(*testCase.Config)
95 if testCase.TransportTLS != useTLS {
96 t.Errorf("expected %v for %#v", testCase.TransportTLS, testCase.Config)
97 }
98 }
99 }
100
101 func TestSetKubernetesDefaultsUserAgent(t *testing.T) {
102 config := &Config{}
103 if err := SetKubernetesDefaults(config); err != nil {
104 t.Errorf("unexpected error: %v", err)
105 }
106 if !strings.Contains(config.UserAgent, "kubernetes/") {
107 t.Errorf("no user agent set: %#v", config)
108 }
109 }
110
111 func TestAdjustVersion(t *testing.T) {
112 assert := assert.New(t)
113 assert.Equal("1.2.3", adjustVersion("1.2.3-alpha4"))
114 assert.Equal("1.2.3", adjustVersion("1.2.3-alpha"))
115 assert.Equal("1.2.3", adjustVersion("1.2.3"))
116 assert.Equal("unknown", adjustVersion(""))
117 }
118
119 func TestAdjustCommit(t *testing.T) {
120 assert := assert.New(t)
121 assert.Equal("1234567", adjustCommit("1234567890"))
122 assert.Equal("123456", adjustCommit("123456"))
123 assert.Equal("unknown", adjustCommit(""))
124 }
125
126 func TestAdjustCommand(t *testing.T) {
127 assert := assert.New(t)
128 assert.Equal("beans", adjustCommand(filepath.Join("home", "bob", "Downloads", "beans")))
129 assert.Equal("beans", adjustCommand(filepath.Join(".", "beans")))
130 assert.Equal("beans", adjustCommand("beans"))
131 assert.Equal("unknown", adjustCommand(""))
132 }
133
134 func TestBuildUserAgent(t *testing.T) {
135 assert.New(t).Equal(
136 "lynx/nicest (beos/itanium) kubernetes/baaaaaaaaad",
137 buildUserAgent(
138 "lynx", "nicest",
139 "beos", "itanium", "baaaaaaaaad"))
140 }
141
142
143 func TestDefaultKubernetesUserAgent(t *testing.T) {
144 assert.New(t).Contains(DefaultKubernetesUserAgent(), "kubernetes")
145 }
146
147 func TestRESTClientRequires(t *testing.T) {
148 if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{NegotiatedSerializer: scheme.Codecs}}); err == nil {
149 t.Errorf("unexpected non-error")
150 }
151 if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: &v1.SchemeGroupVersion}}); err == nil {
152 t.Errorf("unexpected non-error")
153 }
154 if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}}); err != nil {
155 t.Errorf("unexpected error: %v", err)
156 }
157 }
158
159 func TestRESTClientLimiter(t *testing.T) {
160 testCases := []struct {
161 Name string
162 Config Config
163 Limiter flowcontrol.RateLimiter
164 }{
165 {
166 Name: "with no QPS",
167 Config: Config{},
168 Limiter: flowcontrol.NewTokenBucketRateLimiter(5, 10),
169 },
170 {
171 Name: "with QPS:10",
172 Config: Config{QPS: 10},
173 Limiter: flowcontrol.NewTokenBucketRateLimiter(10, 10),
174 },
175 {
176 Name: "with QPS:-1",
177 Config: Config{QPS: -1},
178 Limiter: nil,
179 },
180 {
181 Name: "with RateLimiter",
182 Config: Config{
183 RateLimiter: flowcontrol.NewTokenBucketRateLimiter(11, 12),
184 },
185 Limiter: flowcontrol.NewTokenBucketRateLimiter(11, 12),
186 },
187 }
188 for _, testCase := range testCases {
189 t.Run("Versioned_"+testCase.Name, func(t *testing.T) {
190 config := testCase.Config
191 config.Host = "127.0.0.1"
192 config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}
193 client, err := RESTClientFor(&config)
194 if err != nil {
195 t.Fatalf("unexpected error: %v", err)
196 }
197 if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) {
198 t.Fatalf("unexpected rate limiter: %#v, expected %#v at %s", client.rateLimiter, testCase.Limiter, testCase.Name)
199 }
200 })
201 t.Run("Unversioned_"+testCase.Name, func(t *testing.T) {
202 config := testCase.Config
203 config.Host = "127.0.0.1"
204 config.ContentConfig = ContentConfig{GroupVersion: &v1.SchemeGroupVersion, NegotiatedSerializer: scheme.Codecs}
205 client, err := UnversionedRESTClientFor(&config)
206 if err != nil {
207 t.Fatalf("unexpected error: %v", err)
208 }
209 if !reflect.DeepEqual(testCase.Limiter, client.rateLimiter) {
210 t.Fatalf("unexpected rate limiter: %#v, expected %#v at %s", client.rateLimiter, testCase.Limiter, testCase.Name)
211 }
212 })
213 }
214 }
215
216 type fakeLimiter struct {
217 FakeSaturation float64
218 FakeQPS float32
219 }
220
221 func (t *fakeLimiter) TryAccept() bool {
222 return true
223 }
224
225 func (t *fakeLimiter) Saturation() float64 {
226 return t.FakeSaturation
227 }
228
229 func (t *fakeLimiter) QPS() float32 {
230 return t.FakeQPS
231 }
232
233 func (t *fakeLimiter) Wait(ctx context.Context) error {
234 return nil
235 }
236
237 func (t *fakeLimiter) Stop() {}
238
239 func (t *fakeLimiter) Accept() {}
240
241 type fakeCodec struct{}
242
243 func (c *fakeCodec) Decode([]byte, *schema.GroupVersionKind, runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
244 return nil, nil, nil
245 }
246
247 func (c *fakeCodec) Encode(obj runtime.Object, stream io.Writer) error {
248 return nil
249 }
250
251 func (c *fakeCodec) Identifier() runtime.Identifier {
252 return runtime.Identifier("fake")
253 }
254
255 type fakeRoundTripper struct{}
256
257 func (r *fakeRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
258 return nil, nil
259 }
260
261 var fakeWrapperFunc = func(http.RoundTripper) http.RoundTripper {
262 return &fakeRoundTripper{}
263 }
264
265 type fakeWarningHandler struct{}
266
267 func (f fakeWarningHandler) HandleWarningHeader(code int, agent string, message string) {}
268
269 type fakeNegotiatedSerializer struct{}
270
271 func (n *fakeNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
272 return nil
273 }
274
275 func (n *fakeNegotiatedSerializer) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
276 return &fakeCodec{}
277 }
278
279 func (n *fakeNegotiatedSerializer) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
280 return &fakeCodec{}
281 }
282
283 var fakeDialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) {
284 return nil, fakeDialerError
285 }
286
287 var fakeDialerError = errors.New("fakedialer")
288
289 func fakeProxyFunc(*http.Request) (*url.URL, error) {
290 return nil, errors.New("fakeproxy")
291 }
292
293 type fakeAuthProviderConfigPersister struct{}
294
295 func (fakeAuthProviderConfigPersister) Persist(map[string]string) error {
296 return fakeAuthProviderConfigPersisterError
297 }
298
299 var fakeAuthProviderConfigPersisterError = errors.New("fakeAuthProviderConfigPersisterError")
300
301 func TestAnonymousConfig(t *testing.T) {
302 f := fuzz.New().NilChance(0.0).NumElements(1, 1)
303 f.Funcs(
304 func(r *runtime.Codec, f fuzz.Continue) {
305 codec := &fakeCodec{}
306 f.Fuzz(codec)
307 *r = codec
308 },
309 func(r *http.RoundTripper, f fuzz.Continue) {
310 roundTripper := &fakeRoundTripper{}
311 f.Fuzz(roundTripper)
312 *r = roundTripper
313 },
314 func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) {
315 *fn = fakeWrapperFunc
316 },
317 func(fn *transport.WrapperFunc, f fuzz.Continue) {
318 *fn = fakeWrapperFunc
319 },
320 func(r *runtime.NegotiatedSerializer, f fuzz.Continue) {
321 serializer := &fakeNegotiatedSerializer{}
322 f.Fuzz(serializer)
323 *r = serializer
324 },
325 func(r *flowcontrol.RateLimiter, f fuzz.Continue) {
326 limiter := &fakeLimiter{}
327 f.Fuzz(limiter)
328 *r = limiter
329 },
330 func(h *WarningHandler, f fuzz.Continue) {
331 *h = &fakeWarningHandler{}
332 },
333
334 func(r *AuthProviderConfigPersister, f fuzz.Continue) {},
335 func(r *clientcmdapi.AuthProviderConfig, f fuzz.Continue) {
336 r.Config = map[string]string{}
337 },
338 func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {
339 *r = fakeDialFunc
340 },
341 func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
342 *r = fakeProxyFunc
343 },
344 func(r *runtime.Object, f fuzz.Continue) {
345 unknown := &runtime.Unknown{}
346 f.Fuzz(unknown)
347 *r = unknown
348 },
349 )
350 for i := 0; i < 20; i++ {
351 original := &Config{}
352 f.Fuzz(original)
353 actual := AnonymousClientConfig(original)
354 expected := *original
355
356
357
358 expected.Impersonate = ImpersonationConfig{}
359 expected.BearerToken = ""
360 expected.BearerTokenFile = ""
361 expected.Username = ""
362 expected.Password = ""
363 expected.AuthProvider = nil
364 expected.AuthConfigPersister = nil
365 expected.ExecProvider = nil
366 expected.TLSClientConfig.CertData = nil
367 expected.TLSClientConfig.CertFile = ""
368 expected.TLSClientConfig.KeyData = nil
369 expected.TLSClientConfig.KeyFile = ""
370 expected.Transport = nil
371 expected.WrapTransport = nil
372
373 if actual.Dial != nil {
374 _, actualError := actual.Dial(context.Background(), "", "")
375 _, expectedError := expected.Dial(context.Background(), "", "")
376 if !reflect.DeepEqual(expectedError, actualError) {
377 t.Fatalf("AnonymousClientConfig dropped the Dial field")
378 }
379 }
380 actual.Dial = nil
381 expected.Dial = nil
382
383 if actual.Proxy != nil {
384 _, actualError := actual.Proxy(nil)
385 _, expectedError := expected.Proxy(nil)
386 if !reflect.DeepEqual(expectedError, actualError) {
387 t.Fatalf("AnonymousClientConfig dropped the Proxy field")
388 }
389 }
390 actual.Proxy = nil
391 expected.Proxy = nil
392
393 if diff := cmp.Diff(*actual, expected); diff != "" {
394 t.Fatalf("AnonymousClientConfig dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff)
395 }
396 }
397 }
398
399 func TestCopyConfig(t *testing.T) {
400 f := fuzz.New().NilChance(0.0).NumElements(1, 1)
401 f.Funcs(
402 func(r *runtime.Codec, f fuzz.Continue) {
403 codec := &fakeCodec{}
404 f.Fuzz(codec)
405 *r = codec
406 },
407 func(r *http.RoundTripper, f fuzz.Continue) {
408 roundTripper := &fakeRoundTripper{}
409 f.Fuzz(roundTripper)
410 *r = roundTripper
411 },
412 func(fn *func(http.RoundTripper) http.RoundTripper, f fuzz.Continue) {
413 *fn = fakeWrapperFunc
414 },
415 func(fn *transport.WrapperFunc, f fuzz.Continue) {
416 *fn = fakeWrapperFunc
417 },
418 func(r *runtime.NegotiatedSerializer, f fuzz.Continue) {
419 serializer := &fakeNegotiatedSerializer{}
420 f.Fuzz(serializer)
421 *r = serializer
422 },
423 func(r *flowcontrol.RateLimiter, f fuzz.Continue) {
424 limiter := &fakeLimiter{}
425 f.Fuzz(limiter)
426 *r = limiter
427 },
428 func(h *WarningHandler, f fuzz.Continue) {
429 *h = &fakeWarningHandler{}
430 },
431 func(r *AuthProviderConfigPersister, f fuzz.Continue) {
432 *r = fakeAuthProviderConfigPersister{}
433 },
434 func(r *func(ctx context.Context, network, addr string) (net.Conn, error), f fuzz.Continue) {
435 *r = fakeDialFunc
436 },
437 func(r *func(*http.Request) (*url.URL, error), f fuzz.Continue) {
438 *r = fakeProxyFunc
439 },
440 func(r *runtime.Object, f fuzz.Continue) {
441 unknown := &runtime.Unknown{}
442 f.Fuzz(unknown)
443 *r = unknown
444 },
445 )
446 for i := 0; i < 20; i++ {
447 original := &Config{}
448 f.Fuzz(original)
449 actual := CopyConfig(original)
450 expected := *original
451
452
453
454
455
456
457 if actual.WrapTransport == nil || !reflect.DeepEqual(expected.WrapTransport(nil), &fakeRoundTripper{}) {
458 t.Fatalf("CopyConfig dropped the WrapTransport field")
459 }
460 actual.WrapTransport = nil
461 expected.WrapTransport = nil
462
463 if actual.Dial != nil {
464 _, actualError := actual.Dial(context.Background(), "", "")
465 _, expectedError := expected.Dial(context.Background(), "", "")
466 if !reflect.DeepEqual(expectedError, actualError) {
467 t.Fatalf("CopyConfig dropped the Dial field")
468 }
469 }
470 actual.Dial = nil
471 expected.Dial = nil
472
473 if actual.AuthConfigPersister != nil {
474 actualError := actual.AuthConfigPersister.Persist(nil)
475 expectedError := expected.AuthConfigPersister.Persist(nil)
476 if !reflect.DeepEqual(expectedError, actualError) {
477 t.Fatalf("CopyConfig dropped the Dial field")
478 }
479 }
480 actual.AuthConfigPersister = nil
481 expected.AuthConfigPersister = nil
482
483 if actual.Proxy != nil {
484 _, actualError := actual.Proxy(nil)
485 _, expectedError := expected.Proxy(nil)
486 if !reflect.DeepEqual(expectedError, actualError) {
487 t.Fatalf("CopyConfig dropped the Proxy field")
488 }
489 }
490 actual.Proxy = nil
491 expected.Proxy = nil
492
493 if diff := cmp.Diff(*actual, expected); diff != "" {
494 t.Fatalf("CopyConfig dropped unexpected fields, identify whether they are security related or not (-got, +want): %s", diff)
495 }
496 }
497 }
498
499 func TestConfigStringer(t *testing.T) {
500 formatBytes := func(b []byte) string {
501
502
503 return strings.Replace(fmt.Sprintf("%#v", b), "byte", "uint8", 1)
504 }
505 tests := []struct {
506 desc string
507 c *Config
508 expectContent []string
509 prohibitContent []string
510 }{
511 {
512 desc: "nil config",
513 c: nil,
514 expectContent: []string{"<nil>"},
515 },
516 {
517 desc: "non-sensitive config",
518 c: &Config{
519 Host: "localhost:8080",
520 APIPath: "v1",
521 UserAgent: "gobot",
522 },
523 expectContent: []string{"localhost:8080", "v1", "gobot"},
524 },
525 {
526 desc: "sensitive config",
527 c: &Config{
528 Host: "localhost:8080",
529 Username: "gopher",
530 Password: "g0ph3r",
531 BearerToken: "1234567890",
532 TLSClientConfig: TLSClientConfig{
533 CertFile: "a.crt",
534 KeyFile: "a.key",
535 CertData: []byte("fake cert"),
536 KeyData: []byte("fake key"),
537 },
538 AuthProvider: &clientcmdapi.AuthProviderConfig{
539 Config: map[string]string{"secret": "s3cr3t"},
540 },
541 ExecProvider: &clientcmdapi.ExecConfig{
542 Args: []string{"secret"},
543 Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
544 Config: &runtime.Unknown{Raw: []byte("here is some config data")},
545 },
546 },
547 expectContent: []string{
548 "localhost:8080",
549 "gopher",
550 "a.crt",
551 "a.key",
552 "--- REDACTED ---",
553 formatBytes([]byte("--- REDACTED ---")),
554 formatBytes([]byte("--- TRUNCATED ---")),
555 },
556 prohibitContent: []string{
557 "g0ph3r",
558 "1234567890",
559 formatBytes([]byte("fake cert")),
560 formatBytes([]byte("fake key")),
561 "secret",
562 "s3cr3t",
563 "here is some config data",
564 formatBytes([]byte("super secret password")),
565 },
566 },
567 }
568
569 for _, tt := range tests {
570 t.Run(tt.desc, func(t *testing.T) {
571 got := tt.c.String()
572 t.Logf("formatted config: %q", got)
573
574 for _, expect := range tt.expectContent {
575 if !strings.Contains(got, expect) {
576 t.Errorf("missing expected string %q", expect)
577 }
578 }
579 for _, prohibit := range tt.prohibitContent {
580 if strings.Contains(got, prohibit) {
581 t.Errorf("found prohibited string %q", prohibit)
582 }
583 }
584 })
585 }
586 }
587
588 func TestConfigSprint(t *testing.T) {
589 c := &Config{
590 Host: "localhost:8080",
591 APIPath: "v1",
592 ContentConfig: ContentConfig{
593 AcceptContentTypes: "application/json",
594 ContentType: "application/json",
595 },
596 Username: "gopher",
597 Password: "g0ph3r",
598 BearerToken: "1234567890",
599 Impersonate: ImpersonationConfig{
600 UserName: "gopher2",
601 UID: "uid123",
602 },
603 AuthProvider: &clientcmdapi.AuthProviderConfig{
604 Name: "gopher",
605 Config: map[string]string{"secret": "s3cr3t"},
606 },
607 AuthConfigPersister: fakeAuthProviderConfigPersister{},
608 ExecProvider: &clientcmdapi.ExecConfig{
609 Command: "sudo",
610 Args: []string{"secret"},
611 Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
612 ProvideClusterInfo: true,
613 Config: &runtime.Unknown{Raw: []byte("super secret password")},
614 },
615 TLSClientConfig: TLSClientConfig{
616 CertFile: "a.crt",
617 KeyFile: "a.key",
618 CertData: []byte("fake cert"),
619 KeyData: []byte("fake key"),
620 NextProtos: []string{"h2", "http/1.1"},
621 },
622 UserAgent: "gobot",
623 Transport: &fakeRoundTripper{},
624 WrapTransport: fakeWrapperFunc,
625 QPS: 1,
626 Burst: 2,
627 RateLimiter: &fakeLimiter{},
628 WarningHandler: fakeWarningHandler{},
629 Timeout: 3 * time.Second,
630 Dial: fakeDialFunc,
631 Proxy: fakeProxyFunc,
632 }
633 want := fmt.Sprintf(
634 `&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", UID:"uid123", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.ExecConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: "", ProvideClusterInfo: true, Config: runtime.Object(--- REDACTED ---), StdinUnavailable: false}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), WarningHandler:rest.fakeWarningHandler{}, Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p), Proxy:(func(*http.Request) (*url.URL, error))(%p)}`,
635 c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc, fakeProxyFunc,
636 )
637
638 for _, f := range []string{"%s", "%v", "%+v", "%#v"} {
639 if got := fmt.Sprintf(f, c); want != got {
640 t.Errorf("fmt.Sprintf(%q, c)\ngot: %q\nwant: %q", f, got, want)
641 }
642 }
643 }
644
View as plain text