1
2
3
4
5
6
7
8
9
10
11
12
13
14 package config
15
16 import (
17 "context"
18 "crypto/tls"
19 "crypto/x509"
20 "encoding/json"
21 "errors"
22 "fmt"
23 "io"
24 "net"
25 "net/http"
26 "net/http/httptest"
27 "net/url"
28 "os"
29 "path/filepath"
30 "reflect"
31 "strconv"
32 "strings"
33 "sync"
34 "sync/atomic"
35 "testing"
36 "time"
37
38 "gopkg.in/yaml.v2"
39 )
40
41 const (
42 TLSCAChainPath = "testdata/tls-ca-chain.pem"
43 ServerCertificatePath = "testdata/server.crt"
44 ServerKeyPath = "testdata/server.key"
45 ClientCertificatePath = "testdata/client.crt"
46 ClientKeyNoPassPath = "testdata/client.key"
47 InvalidCA = "testdata/client.key"
48 WrongClientCertPath = "testdata/self-signed-client.crt"
49 WrongClientKeyPath = "testdata/self-signed-client.key"
50 EmptyFile = "testdata/empty"
51 MissingCA = "missing/ca.crt"
52 MissingCert = "missing/cert.crt"
53 MissingKey = "missing/secret.key"
54
55 ExpectedMessage = "I'm here to serve you!!!"
56 ExpectedError = "expected error"
57 AuthorizationCredentials = "theanswertothegreatquestionoflifetheuniverseandeverythingisfortytwo"
58 AuthorizationCredentialsFile = "testdata/bearer.token"
59 AuthorizationType = "APIKEY"
60 BearerToken = AuthorizationCredentials
61 BearerTokenFile = AuthorizationCredentialsFile
62 MissingBearerTokenFile = "missing/bearer.token"
63 ExpectedBearer = "Bearer " + BearerToken
64 ExpectedAuthenticationCredentials = AuthorizationType + " " + BearerToken
65 ExpectedUsername = "arthurdent"
66 ExpectedPassword = "42"
67 ExpectedAccessToken = "12345"
68 )
69
70 var invalidHTTPClientConfigs = []struct {
71 httpClientConfigFile string
72 errMsg string
73 }{
74 {
75 httpClientConfigFile: "testdata/http.conf.bearer-token-and-file-set.bad.yml",
76 errMsg: "at most one of bearer_token & bearer_token_file must be configured",
77 },
78 {
79 httpClientConfigFile: "testdata/http.conf.empty.bad.yml",
80 errMsg: "at most one of basic_auth, oauth2, bearer_token & bearer_token_file must be configured",
81 },
82 {
83 httpClientConfigFile: "testdata/http.conf.basic-auth.too-much.bad.yaml",
84 errMsg: "at most one of basic_auth password & password_file must be configured",
85 },
86 {
87 httpClientConfigFile: "testdata/http.conf.basic-auth.bad-username.yaml",
88 errMsg: "at most one of basic_auth username & username_file must be configured",
89 },
90 {
91 httpClientConfigFile: "testdata/http.conf.mix-bearer-and-creds.bad.yaml",
92 errMsg: "authorization is not compatible with bearer_token & bearer_token_file",
93 },
94 {
95 httpClientConfigFile: "testdata/http.conf.auth-creds-and-file-set.too-much.bad.yaml",
96 errMsg: "at most one of authorization credentials & credentials_file must be configured",
97 },
98 {
99 httpClientConfigFile: "testdata/http.conf.basic-auth-and-auth-creds.too-much.bad.yaml",
100 errMsg: "at most one of basic_auth, oauth2 & authorization must be configured",
101 },
102 {
103 httpClientConfigFile: "testdata/http.conf.basic-auth-and-oauth2.too-much.bad.yaml",
104 errMsg: "at most one of basic_auth, oauth2 & authorization must be configured",
105 },
106 {
107 httpClientConfigFile: "testdata/http.conf.auth-creds-no-basic.bad.yaml",
108 errMsg: `authorization type cannot be set to "basic", use "basic_auth" instead`,
109 },
110 {
111 httpClientConfigFile: "testdata/http.conf.oauth2-secret-and-file-set.bad.yml",
112 errMsg: "at most one of oauth2 client_secret & client_secret_file must be configured",
113 },
114 {
115 httpClientConfigFile: "testdata/http.conf.oauth2-no-client-id.bad.yaml",
116 errMsg: "oauth2 client_id must be configured",
117 },
118 {
119 httpClientConfigFile: "testdata/http.conf.oauth2-no-token-url.bad.yaml",
120 errMsg: "oauth2 token_url must be configured",
121 },
122 {
123 httpClientConfigFile: "testdata/http.conf.proxy-from-env.bad.yaml",
124 errMsg: "if proxy_from_environment is configured, proxy_url must not be configured",
125 },
126 {
127 httpClientConfigFile: "testdata/http.conf.no-proxy.bad.yaml",
128 errMsg: "if proxy_from_environment is configured, no_proxy must not be configured",
129 },
130 {
131 httpClientConfigFile: "testdata/http.conf.no-proxy-without-proxy-url.bad.yaml",
132 errMsg: "if no_proxy is configured, proxy_url must also be configured",
133 },
134 }
135
136 func newTestServer(handler func(w http.ResponseWriter, r *http.Request)) (*httptest.Server, error) {
137 testServer := httptest.NewUnstartedServer(http.HandlerFunc(handler))
138
139 tlsCAChain, err := os.ReadFile(TLSCAChainPath)
140 if err != nil {
141 return nil, fmt.Errorf("Can't read %s", TLSCAChainPath)
142 }
143 serverCertificate, err := tls.LoadX509KeyPair(ServerCertificatePath, ServerKeyPath)
144 if err != nil {
145 return nil, fmt.Errorf("Can't load X509 key pair %s - %s", ServerCertificatePath, ServerKeyPath)
146 }
147
148 rootCAs := x509.NewCertPool()
149 rootCAs.AppendCertsFromPEM(tlsCAChain)
150
151 testServer.TLS = &tls.Config{
152 Certificates: make([]tls.Certificate, 1),
153 RootCAs: rootCAs,
154 ClientAuth: tls.RequireAndVerifyClientCert,
155 ClientCAs: rootCAs,
156 }
157 testServer.TLS.Certificates[0] = serverCertificate
158
159 testServer.StartTLS()
160
161 return testServer, nil
162 }
163
164 func TestNewClientFromConfig(t *testing.T) {
165 newClientValidConfig := []struct {
166 clientConfig HTTPClientConfig
167 handler func(w http.ResponseWriter, r *http.Request)
168 }{
169 {
170 clientConfig: HTTPClientConfig{
171 TLSConfig: TLSConfig{
172 CAFile: "",
173 CertFile: ClientCertificatePath,
174 KeyFile: ClientKeyNoPassPath,
175 ServerName: "",
176 InsecureSkipVerify: true,
177 },
178 },
179 handler: func(w http.ResponseWriter, r *http.Request) {
180 fmt.Fprint(w, ExpectedMessage)
181 },
182 },
183 {
184 clientConfig: HTTPClientConfig{
185 TLSConfig: TLSConfig{
186 CAFile: TLSCAChainPath,
187 CertFile: ClientCertificatePath,
188 KeyFile: ClientKeyNoPassPath,
189 ServerName: "",
190 InsecureSkipVerify: false,
191 },
192 },
193 handler: func(w http.ResponseWriter, r *http.Request) {
194 fmt.Fprint(w, ExpectedMessage)
195 },
196 },
197 {
198 clientConfig: HTTPClientConfig{
199 BearerToken: BearerToken,
200 TLSConfig: TLSConfig{
201 CAFile: TLSCAChainPath,
202 CertFile: ClientCertificatePath,
203 KeyFile: ClientKeyNoPassPath,
204 ServerName: "",
205 InsecureSkipVerify: false,
206 },
207 },
208 handler: func(w http.ResponseWriter, r *http.Request) {
209 bearer := r.Header.Get("Authorization")
210 if bearer != ExpectedBearer {
211 fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
212 ExpectedBearer, bearer)
213 } else {
214 fmt.Fprint(w, ExpectedMessage)
215 }
216 },
217 },
218 {
219 clientConfig: HTTPClientConfig{
220 BearerTokenFile: BearerTokenFile,
221 TLSConfig: TLSConfig{
222 CAFile: TLSCAChainPath,
223 CertFile: ClientCertificatePath,
224 KeyFile: ClientKeyNoPassPath,
225 ServerName: "",
226 InsecureSkipVerify: false,
227 },
228 },
229 handler: func(w http.ResponseWriter, r *http.Request) {
230 bearer := r.Header.Get("Authorization")
231 if bearer != ExpectedBearer {
232 fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
233 ExpectedBearer, bearer)
234 } else {
235 fmt.Fprint(w, ExpectedMessage)
236 }
237 },
238 },
239 {
240 clientConfig: HTTPClientConfig{
241 Authorization: &Authorization{Credentials: BearerToken},
242 TLSConfig: TLSConfig{
243 CAFile: TLSCAChainPath,
244 CertFile: ClientCertificatePath,
245 KeyFile: ClientKeyNoPassPath,
246 ServerName: "",
247 InsecureSkipVerify: false,
248 },
249 },
250 handler: func(w http.ResponseWriter, r *http.Request) {
251 bearer := r.Header.Get("Authorization")
252 if bearer != ExpectedBearer {
253 fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
254 ExpectedBearer, bearer)
255 } else {
256 fmt.Fprint(w, ExpectedMessage)
257 }
258 },
259 },
260 {
261 clientConfig: HTTPClientConfig{
262 Authorization: &Authorization{CredentialsFile: AuthorizationCredentialsFile, Type: AuthorizationType},
263 TLSConfig: TLSConfig{
264 CAFile: TLSCAChainPath,
265 CertFile: ClientCertificatePath,
266 KeyFile: ClientKeyNoPassPath,
267 ServerName: "",
268 InsecureSkipVerify: false,
269 },
270 },
271 handler: func(w http.ResponseWriter, r *http.Request) {
272 bearer := r.Header.Get("Authorization")
273 if bearer != ExpectedAuthenticationCredentials {
274 fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
275 ExpectedAuthenticationCredentials, bearer)
276 } else {
277 fmt.Fprint(w, ExpectedMessage)
278 }
279 },
280 },
281 {
282 clientConfig: HTTPClientConfig{
283 Authorization: &Authorization{
284 Type: AuthorizationType,
285 },
286 TLSConfig: TLSConfig{
287 CAFile: TLSCAChainPath,
288 CertFile: ClientCertificatePath,
289 KeyFile: ClientKeyNoPassPath,
290 ServerName: "",
291 InsecureSkipVerify: false,
292 },
293 },
294 handler: func(w http.ResponseWriter, r *http.Request) {
295 bearer := r.Header.Get("Authorization")
296 if strings.TrimSpace(bearer) != AuthorizationType {
297 fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
298 AuthorizationType, bearer)
299 } else {
300 fmt.Fprint(w, ExpectedMessage)
301 }
302 },
303 },
304 {
305 clientConfig: HTTPClientConfig{
306 Authorization: &Authorization{
307 Credentials: AuthorizationCredentials,
308 Type: AuthorizationType,
309 },
310 TLSConfig: TLSConfig{
311 CAFile: TLSCAChainPath,
312 CertFile: ClientCertificatePath,
313 KeyFile: ClientKeyNoPassPath,
314 ServerName: "",
315 InsecureSkipVerify: false,
316 },
317 },
318 handler: func(w http.ResponseWriter, r *http.Request) {
319 bearer := r.Header.Get("Authorization")
320 if bearer != ExpectedAuthenticationCredentials {
321 fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
322 ExpectedAuthenticationCredentials, bearer)
323 } else {
324 fmt.Fprint(w, ExpectedMessage)
325 }
326 },
327 },
328 {
329 clientConfig: HTTPClientConfig{
330 Authorization: &Authorization{
331 CredentialsFile: BearerTokenFile,
332 },
333 TLSConfig: TLSConfig{
334 CAFile: TLSCAChainPath,
335 CertFile: ClientCertificatePath,
336 KeyFile: ClientKeyNoPassPath,
337 ServerName: "",
338 InsecureSkipVerify: false,
339 },
340 },
341 handler: func(w http.ResponseWriter, r *http.Request) {
342 bearer := r.Header.Get("Authorization")
343 if bearer != ExpectedBearer {
344 fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
345 ExpectedBearer, bearer)
346 } else {
347 fmt.Fprint(w, ExpectedMessage)
348 }
349 },
350 },
351 {
352 clientConfig: HTTPClientConfig{
353 BasicAuth: &BasicAuth{
354 Username: ExpectedUsername,
355 Password: ExpectedPassword,
356 },
357 TLSConfig: TLSConfig{
358 CAFile: TLSCAChainPath,
359 CertFile: ClientCertificatePath,
360 KeyFile: ClientKeyNoPassPath,
361 ServerName: "",
362 InsecureSkipVerify: false,
363 },
364 },
365 handler: func(w http.ResponseWriter, r *http.Request) {
366 username, password, ok := r.BasicAuth()
367 if !ok {
368 fmt.Fprintf(w, "The Authorization header wasn't set")
369 } else if ExpectedUsername != username {
370 fmt.Fprintf(w, "The expected username (%s) differs from the obtained username (%s).", ExpectedUsername, username)
371 } else if ExpectedPassword != password {
372 fmt.Fprintf(w, "The expected password (%s) differs from the obtained password (%s).", ExpectedPassword, password)
373 } else {
374 fmt.Fprint(w, ExpectedMessage)
375 }
376 },
377 },
378 {
379 clientConfig: HTTPClientConfig{
380 FollowRedirects: true,
381 TLSConfig: TLSConfig{
382 CAFile: TLSCAChainPath,
383 CertFile: ClientCertificatePath,
384 KeyFile: ClientKeyNoPassPath,
385 ServerName: "",
386 InsecureSkipVerify: false,
387 },
388 },
389 handler: func(w http.ResponseWriter, r *http.Request) {
390 switch r.URL.Path {
391 case "/redirected":
392 fmt.Fprint(w, ExpectedMessage)
393 default:
394 w.Header().Set("Location", "/redirected")
395 w.WriteHeader(http.StatusFound)
396 fmt.Fprint(w, "It should follow the redirect.")
397 }
398 },
399 },
400 {
401 clientConfig: HTTPClientConfig{
402 FollowRedirects: false,
403 TLSConfig: TLSConfig{
404 CAFile: TLSCAChainPath,
405 CertFile: ClientCertificatePath,
406 KeyFile: ClientKeyNoPassPath,
407 ServerName: "",
408 InsecureSkipVerify: false,
409 },
410 },
411 handler: func(w http.ResponseWriter, r *http.Request) {
412 switch r.URL.Path {
413 case "/redirected":
414 fmt.Fprint(w, "The redirection was followed.")
415 default:
416 w.Header().Set("Location", "/redirected")
417 w.WriteHeader(http.StatusFound)
418 fmt.Fprint(w, ExpectedMessage)
419 }
420 },
421 },
422 {
423 clientConfig: HTTPClientConfig{
424 OAuth2: &OAuth2{
425 ClientID: "ExpectedUsername",
426 TLSConfig: TLSConfig{
427 CAFile: TLSCAChainPath,
428 CertFile: ClientCertificatePath,
429 KeyFile: ClientKeyNoPassPath,
430 ServerName: "",
431 InsecureSkipVerify: false,
432 },
433 },
434 TLSConfig: TLSConfig{
435 CAFile: TLSCAChainPath,
436 CertFile: ClientCertificatePath,
437 KeyFile: ClientKeyNoPassPath,
438 ServerName: "",
439 InsecureSkipVerify: false,
440 },
441 },
442 handler: func(w http.ResponseWriter, r *http.Request) {
443 switch r.URL.Path {
444 case "/token":
445 res, _ := json.Marshal(oauth2TestServerResponse{
446 AccessToken: ExpectedAccessToken,
447 TokenType: "Bearer",
448 })
449 w.Header().Add("Content-Type", "application/json")
450 _, _ = w.Write(res)
451
452 default:
453 authorization := r.Header.Get("Authorization")
454 if authorization != "Bearer "+ExpectedAccessToken {
455 fmt.Fprintf(w, "Expected Authorization header %q, got %q", "Bearer "+ExpectedAccessToken, authorization)
456 } else {
457 fmt.Fprint(w, ExpectedMessage)
458 }
459 }
460 },
461 },
462 {
463 clientConfig: HTTPClientConfig{
464 OAuth2: &OAuth2{
465 ClientID: "ExpectedUsername",
466 ClientSecret: "ExpectedPassword",
467 TLSConfig: TLSConfig{
468 CAFile: TLSCAChainPath,
469 CertFile: ClientCertificatePath,
470 KeyFile: ClientKeyNoPassPath,
471 ServerName: "",
472 InsecureSkipVerify: false,
473 },
474 },
475 TLSConfig: TLSConfig{
476 CAFile: TLSCAChainPath,
477 CertFile: ClientCertificatePath,
478 KeyFile: ClientKeyNoPassPath,
479 ServerName: "",
480 InsecureSkipVerify: false,
481 },
482 },
483 handler: func(w http.ResponseWriter, r *http.Request) {
484 switch r.URL.Path {
485 case "/token":
486 res, _ := json.Marshal(oauth2TestServerResponse{
487 AccessToken: ExpectedAccessToken,
488 TokenType: "Bearer",
489 })
490 w.Header().Add("Content-Type", "application/json")
491 _, _ = w.Write(res)
492
493 default:
494 authorization := r.Header.Get("Authorization")
495 if authorization != "Bearer "+ExpectedAccessToken {
496 fmt.Fprintf(w, "Expected Authorization header %q, got %q", "Bearer "+ExpectedAccessToken, authorization)
497 } else {
498 fmt.Fprint(w, ExpectedMessage)
499 }
500 }
501 },
502 },
503 }
504
505 for _, validConfig := range newClientValidConfig {
506 testServer, err := newTestServer(validConfig.handler)
507 if err != nil {
508 t.Fatal(err.Error())
509 }
510 defer testServer.Close()
511
512 if validConfig.clientConfig.OAuth2 != nil {
513
514
515 validConfig.clientConfig.OAuth2.TokenURL = testServer.URL + "/token"
516 }
517
518 err = validConfig.clientConfig.Validate()
519 if err != nil {
520 t.Fatal(err.Error())
521 }
522 client, err := NewClientFromConfig(validConfig.clientConfig, "test")
523 if err != nil {
524 t.Errorf("Can't create a client from this config: %+v", validConfig.clientConfig)
525 continue
526 }
527
528 response, err := client.Get(testServer.URL)
529 if err != nil {
530 t.Errorf("Can't connect to the test server using this config: %+v: %v", validConfig.clientConfig, err)
531 continue
532 }
533
534 message, err := io.ReadAll(response.Body)
535 response.Body.Close()
536 if err != nil {
537 t.Errorf("Can't read the server response body using this config: %+v", validConfig.clientConfig)
538 continue
539 }
540
541 trimMessage := strings.TrimSpace(string(message))
542 if ExpectedMessage != trimMessage {
543 t.Errorf("The expected message (%s) differs from the obtained message (%s) using this config: %+v",
544 ExpectedMessage, trimMessage, validConfig.clientConfig)
545 }
546 }
547 }
548
549 func TestProxyConfiguration(t *testing.T) {
550 testcases := map[string]struct {
551 testFn string
552 loader func(string) (*HTTPClientConfig, []byte, error)
553 isValid bool
554 }{
555 "good yaml": {
556 testFn: "testdata/http.conf.proxy-headers.good.yml",
557 loader: LoadHTTPConfigFile,
558 isValid: true,
559 },
560 "bad yaml": {
561 testFn: "testdata/http.conf.proxy-headers.bad.yml",
562 loader: LoadHTTPConfigFile,
563 isValid: false,
564 },
565 "good json": {
566 testFn: "testdata/http.conf.proxy-headers.good.json",
567 loader: loadHTTPConfigJSONFile,
568 isValid: true,
569 },
570 "bad json": {
571 testFn: "testdata/http.conf.proxy-headers.bad.json",
572 loader: loadHTTPConfigJSONFile,
573 isValid: false,
574 },
575 }
576
577 for name, tc := range testcases {
578 t.Run(name, func(t *testing.T) {
579 _, _, err := tc.loader(tc.testFn)
580 if tc.isValid {
581 if err != nil {
582 t.Fatalf("Error validating %s: %s", tc.testFn, err)
583 }
584 } else {
585 if err == nil {
586 t.Fatalf("Expecting error validating %s but got %s", tc.testFn, err)
587 }
588 }
589 })
590 }
591 }
592
593 func TestNewClientFromInvalidConfig(t *testing.T) {
594 newClientInvalidConfig := []struct {
595 clientConfig HTTPClientConfig
596 errorMsg string
597 }{
598 {
599 clientConfig: HTTPClientConfig{
600 TLSConfig: TLSConfig{
601 CAFile: MissingCA,
602 InsecureSkipVerify: true,
603 },
604 },
605 errorMsg: fmt.Sprintf("unable to load specified CA cert %s:", MissingCA),
606 },
607 {
608 clientConfig: HTTPClientConfig{
609 TLSConfig: TLSConfig{
610 CAFile: InvalidCA,
611 InsecureSkipVerify: true,
612 },
613 },
614 errorMsg: fmt.Sprintf("unable to use specified CA cert %s", InvalidCA),
615 },
616 }
617
618 for _, invalidConfig := range newClientInvalidConfig {
619 client, err := NewClientFromConfig(invalidConfig.clientConfig, "test")
620 if client != nil {
621 t.Errorf("A client instance was returned instead of nil using this config: %+v", invalidConfig.clientConfig)
622 }
623 if err == nil {
624 t.Errorf("No error was returned using this config: %+v", invalidConfig.clientConfig)
625 }
626 if !strings.Contains(err.Error(), invalidConfig.errorMsg) {
627 t.Errorf("Expected error %q does not contain %q", err.Error(), invalidConfig.errorMsg)
628 }
629 }
630 }
631
632 func TestCustomDialContextFunc(t *testing.T) {
633 dialFn := func(_ context.Context, _, _ string) (net.Conn, error) {
634 return nil, errors.New(ExpectedError)
635 }
636
637 cfg := HTTPClientConfig{}
638 client, err := NewClientFromConfig(cfg, "test", WithDialContextFunc(dialFn))
639 if err != nil {
640 t.Fatalf("Can't create a client from this config: %+v", cfg)
641 }
642
643 _, err = client.Get("http://localhost")
644 if err == nil || !strings.Contains(err.Error(), ExpectedError) {
645 t.Errorf("Expected error %q but got %q", ExpectedError, err)
646 }
647 }
648
649 func TestCustomIdleConnTimeout(t *testing.T) {
650 timeout := time.Second * 5
651
652 cfg := HTTPClientConfig{}
653 rt, err := NewRoundTripperFromConfig(cfg, "test", WithIdleConnTimeout(timeout))
654 if err != nil {
655 t.Fatalf("Can't create a round-tripper from this config: %+v", cfg)
656 }
657
658 transport, ok := rt.(*http.Transport)
659 if !ok {
660 t.Fatalf("Unexpected transport: %+v", transport)
661 }
662
663 if transport.IdleConnTimeout != timeout {
664 t.Fatalf("Unexpected idle connection timeout: %+v", timeout)
665 }
666 }
667
668 func TestMissingBearerAuthFile(t *testing.T) {
669 cfg := HTTPClientConfig{
670 BearerTokenFile: MissingBearerTokenFile,
671 TLSConfig: TLSConfig{
672 CAFile: TLSCAChainPath,
673 CertFile: ClientCertificatePath,
674 KeyFile: ClientKeyNoPassPath,
675 ServerName: "",
676 InsecureSkipVerify: false,
677 },
678 }
679 handler := func(w http.ResponseWriter, r *http.Request) {
680 bearer := r.Header.Get("Authorization")
681 if bearer != ExpectedBearer {
682 fmt.Fprintf(w, "The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
683 ExpectedBearer, bearer)
684 } else {
685 fmt.Fprint(w, ExpectedMessage)
686 }
687 }
688
689 testServer, err := newTestServer(handler)
690 if err != nil {
691 t.Fatal(err.Error())
692 }
693 defer testServer.Close()
694
695 client, err := NewClientFromConfig(cfg, "test")
696 if err != nil {
697 t.Fatal(err)
698 }
699
700 _, err = client.Get(testServer.URL)
701 if err == nil {
702 t.Fatal("No error is returned here")
703 }
704
705 if !strings.Contains(err.Error(), "unable to read authorization credentials file missing/bearer.token: open missing/bearer.token: no such file or directory") {
706 t.Fatal("wrong error message being returned")
707 }
708 }
709
710 func TestBearerAuthRoundTripper(t *testing.T) {
711 const (
712 newBearerToken = "goodbyeandthankyouforthefish"
713 )
714
715 fakeRoundTripper := NewRoundTripCheckRequest(func(req *http.Request) {
716 bearer := req.Header.Get("Authorization")
717 if bearer != ExpectedBearer {
718 t.Errorf("The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
719 ExpectedBearer, bearer)
720 }
721 }, nil, nil)
722
723
724 bearerAuthRoundTripper := NewAuthorizationCredentialsRoundTripper("Bearer", BearerToken, fakeRoundTripper)
725 request, _ := http.NewRequest("GET", "/hitchhiker", nil)
726 request.Header.Set("User-Agent", "Douglas Adams mind")
727 _, err := bearerAuthRoundTripper.RoundTrip(request)
728 if err != nil {
729 t.Errorf("unexpected error while executing RoundTrip: %s", err.Error())
730 }
731
732
733 bearerAuthRoundTripperShouldNotModifyExistingAuthorization := NewAuthorizationCredentialsRoundTripper("Bearer", newBearerToken, fakeRoundTripper)
734 request, _ = http.NewRequest("GET", "/hitchhiker", nil)
735 request.Header.Set("Authorization", ExpectedBearer)
736 _, err = bearerAuthRoundTripperShouldNotModifyExistingAuthorization.RoundTrip(request)
737 if err != nil {
738 t.Errorf("unexpected error while executing RoundTrip: %s", err.Error())
739 }
740 }
741
742 func TestBearerAuthFileRoundTripper(t *testing.T) {
743 fakeRoundTripper := NewRoundTripCheckRequest(func(req *http.Request) {
744 bearer := req.Header.Get("Authorization")
745 if bearer != ExpectedBearer {
746 t.Errorf("The expected Bearer Authorization (%s) differs from the obtained Bearer Authorization (%s)",
747 ExpectedBearer, bearer)
748 }
749 }, nil, nil)
750
751
752 bearerAuthRoundTripper := NewAuthorizationCredentialsFileRoundTripper("Bearer", BearerTokenFile, fakeRoundTripper)
753 request, _ := http.NewRequest("GET", "/hitchhiker", nil)
754 request.Header.Set("User-Agent", "Douglas Adams mind")
755 _, err := bearerAuthRoundTripper.RoundTrip(request)
756 if err != nil {
757 t.Errorf("unexpected error while executing RoundTrip: %s", err.Error())
758 }
759
760
761 bearerAuthRoundTripperShouldNotModifyExistingAuthorization := NewAuthorizationCredentialsFileRoundTripper("Bearer", MissingBearerTokenFile, fakeRoundTripper)
762 request, _ = http.NewRequest("GET", "/hitchhiker", nil)
763 request.Header.Set("Authorization", ExpectedBearer)
764 _, err = bearerAuthRoundTripperShouldNotModifyExistingAuthorization.RoundTrip(request)
765 if err != nil {
766 t.Errorf("unexpected error while executing RoundTrip: %s", err.Error())
767 }
768 }
769
770 func TestTLSConfig(t *testing.T) {
771 configTLSConfig := TLSConfig{
772 CAFile: TLSCAChainPath,
773 CertFile: ClientCertificatePath,
774 KeyFile: ClientKeyNoPassPath,
775 ServerName: "localhost",
776 InsecureSkipVerify: false,
777 }
778
779 tlsCAChain, err := os.ReadFile(TLSCAChainPath)
780 if err != nil {
781 t.Fatalf("Can't read the CA certificate chain (%s)",
782 TLSCAChainPath)
783 }
784 rootCAs := x509.NewCertPool()
785 rootCAs.AppendCertsFromPEM(tlsCAChain)
786
787 expectedTLSConfig := &tls.Config{
788 RootCAs: rootCAs,
789 ServerName: configTLSConfig.ServerName,
790 InsecureSkipVerify: configTLSConfig.InsecureSkipVerify,
791 }
792
793 tlsConfig, err := NewTLSConfig(&configTLSConfig)
794 if err != nil {
795 t.Fatalf("Can't create a new TLS Config from a configuration (%s).", err)
796 }
797
798 clientCertificate, err := tls.LoadX509KeyPair(ClientCertificatePath, ClientKeyNoPassPath)
799 if err != nil {
800 t.Fatalf("Can't load the client key pair ('%s' and '%s'). Reason: %s",
801 ClientCertificatePath, ClientKeyNoPassPath, err)
802 }
803 cert, err := tlsConfig.GetClientCertificate(nil)
804 if err != nil {
805 t.Fatalf("unexpected error returned by tlsConfig.GetClientCertificate(): %s", err)
806 }
807 if !reflect.DeepEqual(cert, &clientCertificate) {
808 t.Fatalf("Unexpected client certificate result: \n\n%+v\n expected\n\n%+v", cert, clientCertificate)
809 }
810
811
812
813
814 if !reflect.DeepEqual(tlsConfig.RootCAs.Subjects(), expectedTLSConfig.RootCAs.Subjects()) {
815 t.Fatalf("Unexpected RootCAs result: \n\n%+v\n expected\n\n%+v", tlsConfig.RootCAs.Subjects(), expectedTLSConfig.RootCAs.Subjects())
816 }
817 tlsConfig.RootCAs = nil
818 expectedTLSConfig.RootCAs = nil
819
820
821 tlsConfig.GetClientCertificate = nil
822
823 if !reflect.DeepEqual(tlsConfig, expectedTLSConfig) {
824 t.Fatalf("Unexpected TLS Config result: \n\n%+v\n expected\n\n%+v", tlsConfig, expectedTLSConfig)
825 }
826 }
827
828 func TestTLSConfigEmpty(t *testing.T) {
829 configTLSConfig := TLSConfig{
830 InsecureSkipVerify: true,
831 }
832
833 expectedTLSConfig := &tls.Config{
834 InsecureSkipVerify: configTLSConfig.InsecureSkipVerify,
835 }
836
837 tlsConfig, err := NewTLSConfig(&configTLSConfig)
838 if err != nil {
839 t.Fatalf("Can't create a new TLS Config from a configuration (%s).", err)
840 }
841
842 if !reflect.DeepEqual(tlsConfig, expectedTLSConfig) {
843 t.Fatalf("Unexpected TLS Config result: \n\n%+v\n expected\n\n%+v", tlsConfig, expectedTLSConfig)
844 }
845 }
846
847 func TestTLSConfigInvalidCA(t *testing.T) {
848 invalidTLSConfig := []struct {
849 configTLSConfig TLSConfig
850 errorMessage string
851 }{
852 {
853 configTLSConfig: TLSConfig{
854 CAFile: MissingCA,
855 CertFile: "",
856 KeyFile: "",
857 ServerName: "",
858 InsecureSkipVerify: false,
859 },
860 errorMessage: fmt.Sprintf("unable to load specified CA cert %s:", MissingCA),
861 },
862 {
863 configTLSConfig: TLSConfig{
864 CAFile: "",
865 CertFile: MissingCert,
866 KeyFile: ClientKeyNoPassPath,
867 ServerName: "",
868 InsecureSkipVerify: false,
869 },
870 errorMessage: fmt.Sprintf("unable to read specified client cert (%s):", MissingCert),
871 },
872 {
873 configTLSConfig: TLSConfig{
874 CAFile: "",
875 CertFile: ClientCertificatePath,
876 KeyFile: MissingKey,
877 ServerName: "",
878 InsecureSkipVerify: false,
879 },
880 errorMessage: fmt.Sprintf("unable to read specified client key (%s):", MissingKey),
881 },
882 {
883 configTLSConfig: TLSConfig{
884 CAFile: "",
885 Cert: readFile(t, ClientCertificatePath),
886 CertFile: ClientCertificatePath,
887 KeyFile: ClientKeyNoPassPath,
888 ServerName: "",
889 InsecureSkipVerify: false,
890 },
891 errorMessage: "at most one of cert and cert_file must be configured",
892 },
893 {
894 configTLSConfig: TLSConfig{
895 CAFile: "",
896 CertFile: ClientCertificatePath,
897 Key: Secret(readFile(t, ClientKeyNoPassPath)),
898 KeyFile: ClientKeyNoPassPath,
899 ServerName: "",
900 InsecureSkipVerify: false,
901 },
902 errorMessage: "at most one of key and key_file must be configured",
903 },
904 }
905
906 for _, anInvalididTLSConfig := range invalidTLSConfig {
907 tlsConfig, err := NewTLSConfig(&anInvalididTLSConfig.configTLSConfig)
908 if tlsConfig != nil && err == nil {
909 t.Errorf("The TLS Config could be created even with this %+v", anInvalididTLSConfig.configTLSConfig)
910 continue
911 }
912 if !strings.Contains(err.Error(), anInvalididTLSConfig.errorMessage) {
913 t.Errorf("The expected error should contain %s, but got %s", anInvalididTLSConfig.errorMessage, err)
914 }
915 }
916 }
917
918 func TestBasicAuthNoPassword(t *testing.T) {
919 cfg, _, err := LoadHTTPConfigFile("testdata/http.conf.basic-auth.no-password.yaml")
920 if err != nil {
921 t.Fatalf("Error loading HTTP client config: %v", err)
922 }
923 client, err := NewClientFromConfig(*cfg, "test")
924 if err != nil {
925 t.Fatalf("Error creating HTTP Client: %v", err)
926 }
927
928 rt, ok := client.Transport.(*basicAuthRoundTripper)
929 if !ok {
930 t.Fatalf("Error casting to basic auth transport, %v", client.Transport)
931 }
932
933 if rt.username != "user" {
934 t.Errorf("Bad HTTP client username: %s", rt.username)
935 }
936 if string(rt.password) != "" {
937 t.Errorf("Expected empty HTTP client password: %s", rt.password)
938 }
939 if string(rt.passwordFile) != "" {
940 t.Errorf("Expected empty HTTP client passwordFile: %s", rt.passwordFile)
941 }
942 }
943
944 func TestBasicAuthNoUsername(t *testing.T) {
945 cfg, _, err := LoadHTTPConfigFile("testdata/http.conf.basic-auth.no-username.yaml")
946 if err != nil {
947 t.Fatalf("Error loading HTTP client config: %v", err)
948 }
949 client, err := NewClientFromConfig(*cfg, "test")
950 if err != nil {
951 t.Fatalf("Error creating HTTP Client: %v", err)
952 }
953
954 rt, ok := client.Transport.(*basicAuthRoundTripper)
955 if !ok {
956 t.Fatalf("Error casting to basic auth transport, %v", client.Transport)
957 }
958
959 if rt.username != "" {
960 t.Errorf("Got unexpected username: %s", rt.username)
961 }
962 if string(rt.password) != "secret" {
963 t.Errorf("Unexpected HTTP client password: %s", string(rt.password))
964 }
965 if string(rt.passwordFile) != "" {
966 t.Errorf("Expected empty HTTP client passwordFile: %s", rt.passwordFile)
967 }
968 }
969
970 func TestBasicAuthPasswordFile(t *testing.T) {
971 cfg, _, err := LoadHTTPConfigFile("testdata/http.conf.basic-auth.good.yaml")
972 if err != nil {
973 t.Fatalf("Error loading HTTP client config: %v", err)
974 }
975 client, err := NewClientFromConfig(*cfg, "test")
976 if err != nil {
977 t.Fatalf("Error creating HTTP Client: %v", err)
978 }
979
980 rt, ok := client.Transport.(*basicAuthRoundTripper)
981 if !ok {
982 t.Fatalf("Error casting to basic auth transport, %v", client.Transport)
983 }
984
985 if rt.username != "user" {
986 t.Errorf("Bad HTTP client username: %s", rt.username)
987 }
988 if string(rt.password) != "" {
989 t.Errorf("Bad HTTP client password: %s", rt.password)
990 }
991 if string(rt.passwordFile) != "testdata/basic-auth-password" {
992 t.Errorf("Bad HTTP client passwordFile: %s", rt.passwordFile)
993 }
994 }
995
996 func TestBasicUsernameFile(t *testing.T) {
997 cfg, _, err := LoadHTTPConfigFile("testdata/http.conf.basic-auth.username-file.good.yaml")
998 if err != nil {
999 t.Fatalf("Error loading HTTP client config: %v", err)
1000 }
1001 client, err := NewClientFromConfig(*cfg, "test")
1002 if err != nil {
1003 t.Fatalf("Error creating HTTP Client: %v", err)
1004 }
1005
1006 rt, ok := client.Transport.(*basicAuthRoundTripper)
1007 if !ok {
1008 t.Fatalf("Error casting to basic auth transport, %v", client.Transport)
1009 }
1010
1011 if rt.username != "" {
1012 t.Errorf("Bad HTTP client username: %s", rt.username)
1013 }
1014 if string(rt.usernameFile) != "testdata/basic-auth-username" {
1015 t.Errorf("Bad HTTP client usernameFile: %s", rt.usernameFile)
1016 }
1017 if string(rt.passwordFile) != "testdata/basic-auth-password" {
1018 t.Errorf("Bad HTTP client passwordFile: %s", rt.passwordFile)
1019 }
1020 }
1021
1022 func getCertificateBlobs(t *testing.T) map[string][]byte {
1023 files := []string{
1024 TLSCAChainPath,
1025 ClientCertificatePath,
1026 ClientKeyNoPassPath,
1027 ServerCertificatePath,
1028 ServerKeyPath,
1029 WrongClientCertPath,
1030 WrongClientKeyPath,
1031 EmptyFile,
1032 }
1033 bs := make(map[string][]byte, len(files)+1)
1034 for _, f := range files {
1035 b, err := os.ReadFile(f)
1036 if err != nil {
1037 t.Fatal(err)
1038 }
1039 bs[f] = b
1040 }
1041
1042 return bs
1043 }
1044
1045 func writeCertificate(bs map[string][]byte, src string, dst string) {
1046 b, ok := bs[src]
1047 if !ok {
1048 panic(fmt.Sprintf("Couldn't find %q in bs", src))
1049 }
1050 if err := os.WriteFile(dst, b, 0o664); err != nil {
1051 panic(err)
1052 }
1053 }
1054
1055 func TestTLSRoundTripper(t *testing.T) {
1056 bs := getCertificateBlobs(t)
1057
1058 tmpDir, err := os.MkdirTemp("", "tlsroundtripper")
1059 if err != nil {
1060 t.Fatal("Failed to create tmp dir", err)
1061 }
1062 defer os.RemoveAll(tmpDir)
1063
1064 ca, cert, key := filepath.Join(tmpDir, "ca"), filepath.Join(tmpDir, "cert"), filepath.Join(tmpDir, "key")
1065
1066 handler := func(w http.ResponseWriter, r *http.Request) {
1067 fmt.Fprint(w, ExpectedMessage)
1068 }
1069 testServer, err := newTestServer(handler)
1070 if err != nil {
1071 t.Fatal(err.Error())
1072 }
1073 defer testServer.Close()
1074
1075 testCases := []struct {
1076 ca string
1077 cert string
1078 key string
1079
1080 errMsg string
1081 }{
1082 {
1083
1084 ca: TLSCAChainPath,
1085 cert: ClientCertificatePath,
1086 key: ClientKeyNoPassPath,
1087 },
1088 {
1089
1090 ca: ClientCertificatePath,
1091 cert: ClientCertificatePath,
1092 key: ClientKeyNoPassPath,
1093
1094 errMsg: "certificate signed by unknown authority",
1095 },
1096 {
1097
1098 ca: TLSCAChainPath,
1099 cert: WrongClientCertPath,
1100 key: WrongClientKeyPath,
1101
1102 errMsg: "remote error: tls",
1103 },
1104 {
1105
1106 ca: EmptyFile,
1107 cert: ClientCertificatePath,
1108 key: ClientKeyNoPassPath,
1109
1110 errMsg: "unable to use specified CA cert",
1111 },
1112 {
1113
1114 ca: TLSCAChainPath,
1115 cert: EmptyFile,
1116 key: ClientKeyNoPassPath,
1117
1118 errMsg: "failed to find any PEM data in certificate input",
1119 },
1120 {
1121
1122 ca: TLSCAChainPath,
1123 cert: ClientCertificatePath,
1124 key: EmptyFile,
1125
1126 errMsg: "failed to find any PEM data in key input",
1127 },
1128 {
1129
1130 ca: TLSCAChainPath,
1131 cert: ClientCertificatePath,
1132 key: ClientKeyNoPassPath,
1133 },
1134 }
1135
1136 cfg := HTTPClientConfig{
1137 TLSConfig: TLSConfig{
1138 CAFile: ca,
1139 CertFile: cert,
1140 KeyFile: key,
1141 InsecureSkipVerify: false,
1142 },
1143 }
1144
1145 var c *http.Client
1146 for i, tc := range testCases {
1147 tc := tc
1148 t.Run(strconv.Itoa(i), func(t *testing.T) {
1149 writeCertificate(bs, tc.ca, ca)
1150 writeCertificate(bs, tc.cert, cert)
1151 writeCertificate(bs, tc.key, key)
1152 if c == nil {
1153 c, err = NewClientFromConfig(cfg, "test")
1154 if err != nil {
1155 t.Fatalf("Error creating HTTP Client: %v", err)
1156 }
1157 }
1158
1159 req, err := http.NewRequest(http.MethodGet, testServer.URL, nil)
1160 if err != nil {
1161 t.Fatalf("Error creating HTTP request: %v", err)
1162 }
1163 r, err := c.Do(req)
1164 if len(tc.errMsg) > 0 {
1165 if err == nil {
1166 r.Body.Close()
1167 t.Fatalf("Could connect to the test server.")
1168 }
1169 if !strings.Contains(err.Error(), tc.errMsg) {
1170 t.Fatalf("Expected error message to contain %q, got %q", tc.errMsg, err)
1171 }
1172 return
1173 }
1174
1175 if err != nil {
1176 t.Fatalf("Can't connect to the test server")
1177 }
1178
1179 b, err := io.ReadAll(r.Body)
1180 r.Body.Close()
1181 if err != nil {
1182 t.Errorf("Can't read the server response body")
1183 }
1184
1185 got := strings.TrimSpace(string(b))
1186 if ExpectedMessage != got {
1187 t.Errorf("The expected message %q differs from the obtained message %q", ExpectedMessage, got)
1188 }
1189 })
1190 }
1191 }
1192
1193 func TestTLSRoundTripper_Inline(t *testing.T) {
1194 handler := func(w http.ResponseWriter, r *http.Request) {
1195 fmt.Fprint(w, ExpectedMessage)
1196 }
1197 testServer, err := newTestServer(handler)
1198 if err != nil {
1199 t.Fatal(err.Error())
1200 }
1201 defer testServer.Close()
1202
1203 testCases := []struct {
1204 caText, caFile string
1205 certText, certFile string
1206 keyText, keyFile string
1207
1208 errMsg string
1209 }{
1210 {
1211
1212 caFile: TLSCAChainPath,
1213 certFile: ClientCertificatePath,
1214 keyFile: ClientKeyNoPassPath,
1215 },
1216 {
1217
1218 caText: readFile(t, TLSCAChainPath),
1219 certFile: ClientCertificatePath,
1220 keyFile: ClientKeyNoPassPath,
1221 },
1222 {
1223
1224 caFile: TLSCAChainPath,
1225 certText: readFile(t, ClientCertificatePath),
1226 keyFile: ClientKeyNoPassPath,
1227 },
1228 {
1229
1230 caFile: TLSCAChainPath,
1231 certFile: ClientCertificatePath,
1232 keyText: readFile(t, ClientKeyNoPassPath),
1233 },
1234 {
1235
1236 caText: readFile(t, TLSCAChainPath),
1237 certText: readFile(t, ClientCertificatePath),
1238 keyText: readFile(t, ClientKeyNoPassPath),
1239 },
1240
1241 {
1242
1243 caText: "badca",
1244 certText: readFile(t, ClientCertificatePath),
1245 keyText: readFile(t, ClientKeyNoPassPath),
1246
1247 errMsg: "unable to use inline CA cert",
1248 },
1249 {
1250
1251 caText: readFile(t, TLSCAChainPath),
1252 certText: "badcert",
1253 keyText: readFile(t, ClientKeyNoPassPath),
1254
1255 errMsg: "failed to find any PEM data in certificate input",
1256 },
1257 {
1258
1259 caText: readFile(t, TLSCAChainPath),
1260 certText: readFile(t, ClientCertificatePath),
1261 keyText: "badkey",
1262
1263 errMsg: "failed to find any PEM data in key input",
1264 },
1265 }
1266
1267 for i, tc := range testCases {
1268 tc := tc
1269 t.Run(strconv.Itoa(i), func(t *testing.T) {
1270 cfg := HTTPClientConfig{
1271 TLSConfig: TLSConfig{
1272 CA: tc.caText,
1273 CAFile: tc.caFile,
1274 Cert: tc.certText,
1275 CertFile: tc.certFile,
1276 Key: Secret(tc.keyText),
1277 KeyFile: tc.keyFile,
1278 InsecureSkipVerify: false,
1279 },
1280 }
1281
1282 c, err := NewClientFromConfig(cfg, "test")
1283 if tc.errMsg != "" {
1284 if !strings.Contains(err.Error(), tc.errMsg) {
1285 t.Fatalf("Expected error message to contain %q, got %q", tc.errMsg, err)
1286 }
1287 return
1288 } else if err != nil {
1289 t.Fatalf("Error creating HTTP Client: %v", err)
1290 }
1291
1292 req, err := http.NewRequest(http.MethodGet, testServer.URL, nil)
1293 if err != nil {
1294 t.Fatalf("Error creating HTTP request: %v", err)
1295 }
1296 r, err := c.Do(req)
1297 if err != nil {
1298 t.Fatalf("Can't connect to the test server")
1299 }
1300
1301 b, err := io.ReadAll(r.Body)
1302 r.Body.Close()
1303 if err != nil {
1304 t.Errorf("Can't read the server response body")
1305 }
1306
1307 got := strings.TrimSpace(string(b))
1308 if ExpectedMessage != got {
1309 t.Errorf("The expected message %q differs from the obtained message %q", ExpectedMessage, got)
1310 }
1311 })
1312 }
1313 }
1314
1315 func TestTLSRoundTripperRaces(t *testing.T) {
1316 bs := getCertificateBlobs(t)
1317
1318 tmpDir, err := os.MkdirTemp("", "tlsroundtripper")
1319 if err != nil {
1320 t.Fatal("Failed to create tmp dir", err)
1321 }
1322 defer os.RemoveAll(tmpDir)
1323
1324 ca, cert, key := filepath.Join(tmpDir, "ca"), filepath.Join(tmpDir, "cert"), filepath.Join(tmpDir, "key")
1325
1326 handler := func(w http.ResponseWriter, r *http.Request) {
1327 fmt.Fprint(w, ExpectedMessage)
1328 }
1329 testServer, err := newTestServer(handler)
1330 if err != nil {
1331 t.Fatal(err.Error())
1332 }
1333 defer testServer.Close()
1334
1335 cfg := HTTPClientConfig{
1336 TLSConfig: TLSConfig{
1337 CAFile: ca,
1338 CertFile: cert,
1339 KeyFile: key,
1340 InsecureSkipVerify: false,
1341 },
1342 }
1343
1344 var c *http.Client
1345 writeCertificate(bs, TLSCAChainPath, ca)
1346 writeCertificate(bs, ClientCertificatePath, cert)
1347 writeCertificate(bs, ClientKeyNoPassPath, key)
1348 c, err = NewClientFromConfig(cfg, "test")
1349 if err != nil {
1350 t.Fatalf("Error creating HTTP Client: %v", err)
1351 }
1352
1353 var wg sync.WaitGroup
1354 ch := make(chan struct{})
1355 var total, ok int64
1356
1357 for i := 0; i < 10; i++ {
1358 wg.Add(1)
1359 go func() {
1360 defer wg.Done()
1361 for {
1362 select {
1363 case <-ch:
1364 return
1365 default:
1366 atomic.AddInt64(&total, 1)
1367 r, err := c.Get(testServer.URL)
1368 if err == nil {
1369 r.Body.Close()
1370 atomic.AddInt64(&ok, 1)
1371 }
1372 }
1373 }
1374 }()
1375 }
1376
1377
1378 wg.Add(1)
1379 go func() {
1380 defer wg.Done()
1381 i := 0
1382 for {
1383 tick := time.NewTicker(10 * time.Millisecond)
1384 <-tick.C
1385 if i%2 == 0 {
1386 writeCertificate(bs, ClientCertificatePath, ca)
1387 } else {
1388 writeCertificate(bs, TLSCAChainPath, ca)
1389 }
1390 i++
1391 if i > 100 {
1392 close(ch)
1393 return
1394 }
1395 }
1396 }()
1397
1398 wg.Wait()
1399 if ok == total {
1400 t.Fatalf("Expecting some requests to fail but got %d/%d successful requests", ok, total)
1401 }
1402 }
1403
1404 func TestHideHTTPClientConfigSecrets(t *testing.T) {
1405 c, _, err := LoadHTTPConfigFile("testdata/http.conf.good.yml")
1406 if err != nil {
1407 t.Errorf("Error parsing %s: %s", "testdata/http.conf.good.yml", err)
1408 }
1409
1410
1411 s := c.String()
1412 if strings.Contains(s, "mysecret") {
1413 t.Fatal("http client config's String method reveals authentication credentials.")
1414 }
1415 }
1416
1417 func TestDefaultFollowRedirect(t *testing.T) {
1418 cfg, _, err := LoadHTTPConfigFile("testdata/http.conf.good.yml")
1419 if err != nil {
1420 t.Errorf("Error loading HTTP client config: %v", err)
1421 }
1422 if !cfg.FollowRedirects {
1423 t.Errorf("follow_redirects should be true")
1424 }
1425 }
1426
1427 func TestValidateHTTPConfig(t *testing.T) {
1428 cfg, _, err := LoadHTTPConfigFile("testdata/http.conf.good.yml")
1429 if err != nil {
1430 t.Errorf("Error loading HTTP client config: %v", err)
1431 }
1432 err = cfg.Validate()
1433 if err != nil {
1434 t.Fatalf("Error validating %s: %s", "testdata/http.conf.good.yml", err)
1435 }
1436 }
1437
1438 func TestInvalidHTTPConfigs(t *testing.T) {
1439 for _, ee := range invalidHTTPClientConfigs {
1440 _, _, err := LoadHTTPConfigFile(ee.httpClientConfigFile)
1441 if err == nil {
1442 t.Error("Expected error with config but got none")
1443 continue
1444 }
1445 if !strings.Contains(err.Error(), ee.errMsg) {
1446 t.Errorf("Expected error for invalid HTTP client configuration to contain %q but got: %s", ee.errMsg, err)
1447 }
1448 }
1449 }
1450
1451 type roundTrip struct {
1452 theResponse *http.Response
1453 theError error
1454 }
1455
1456 func (rt *roundTrip) RoundTrip(r *http.Request) (*http.Response, error) {
1457 return rt.theResponse, rt.theError
1458 }
1459
1460 type roundTripCheckRequest struct {
1461 checkRequest func(*http.Request)
1462 roundTrip
1463 }
1464
1465 func (rt *roundTripCheckRequest) RoundTrip(r *http.Request) (*http.Response, error) {
1466 rt.checkRequest(r)
1467 return rt.theResponse, rt.theError
1468 }
1469
1470
1471
1472 func NewRoundTripCheckRequest(checkRequest func(*http.Request), theResponse *http.Response, theError error) http.RoundTripper {
1473 return &roundTripCheckRequest{
1474 checkRequest: checkRequest,
1475 roundTrip: roundTrip{
1476 theResponse: theResponse,
1477 theError: theError,
1478 },
1479 }
1480 }
1481
1482 type oauth2TestServerResponse struct {
1483 AccessToken string `json:"access_token"`
1484 TokenType string `json:"token_type"`
1485 }
1486
1487 type testOAuthServer struct {
1488 tokenTS *httptest.Server
1489 ts *httptest.Server
1490 }
1491
1492
1493 func newTestOAuthServer(t testing.TB, expectedAuth *string) testOAuthServer {
1494 var previousAuth string
1495 tokenTS := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1496 auth := r.Header.Get("Authorization")
1497 if auth != *expectedAuth {
1498 t.Fatalf("bad auth, expected %s, got %s", *expectedAuth, auth)
1499 }
1500 if auth == previousAuth {
1501 t.Fatal("token endpoint called twice")
1502 }
1503 previousAuth = auth
1504 res, _ := json.Marshal(oauth2TestServerResponse{
1505 AccessToken: "12345",
1506 TokenType: "Bearer",
1507 })
1508 w.Header().Add("Content-Type", "application/json")
1509 _, _ = w.Write(res)
1510 }))
1511 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1512 auth := r.Header.Get("Authorization")
1513 if auth != "Bearer 12345" {
1514 t.Fatalf("bad auth, expected %s, got %s", "Bearer 12345", auth)
1515 }
1516 fmt.Fprintln(w, "Hello, client")
1517 }))
1518 return testOAuthServer{
1519 tokenTS: tokenTS,
1520 ts: ts,
1521 }
1522 }
1523
1524 func (s *testOAuthServer) url() string {
1525 return s.ts.URL
1526 }
1527
1528 func (s *testOAuthServer) tokenURL() string {
1529 return s.tokenTS.URL
1530 }
1531
1532 func (s *testOAuthServer) close() {
1533 s.tokenTS.Close()
1534 s.ts.Close()
1535 }
1536
1537 func TestOAuth2(t *testing.T) {
1538 var expectedAuth string
1539 ts := newTestOAuthServer(t, &expectedAuth)
1540 defer ts.close()
1541
1542 yamlConfig := fmt.Sprintf(`
1543 client_id: 1
1544 client_secret: 2
1545 scopes:
1546 - A
1547 - B
1548 token_url: %s
1549 endpoint_params:
1550 hi: hello
1551 `, ts.tokenURL())
1552 expectedConfig := OAuth2{
1553 ClientID: "1",
1554 ClientSecret: "2",
1555 Scopes: []string{"A", "B"},
1556 EndpointParams: map[string]string{"hi": "hello"},
1557 TokenURL: ts.tokenURL(),
1558 }
1559
1560 var unmarshalledConfig OAuth2
1561 if err := yaml.Unmarshal([]byte(yamlConfig), &unmarshalledConfig); err != nil {
1562 t.Fatalf("Expected no error unmarshalling yaml, got %v", err)
1563 }
1564 if !reflect.DeepEqual(unmarshalledConfig, expectedConfig) {
1565 t.Fatalf("Got unmarshalled config %v, expected %v", unmarshalledConfig, expectedConfig)
1566 }
1567
1568 rt := NewOAuth2RoundTripper(&expectedConfig, http.DefaultTransport, &defaultHTTPClientOptions)
1569
1570 client := http.Client{
1571 Transport: rt,
1572 }
1573
1574
1575 expectedAuth = "Basic MToy"
1576 resp, err := client.Get(ts.url())
1577 if err != nil {
1578 t.Fatal(err)
1579 }
1580
1581 authorization := resp.Request.Header.Get("Authorization")
1582 if authorization != "Bearer 12345" {
1583 t.Fatalf("Expected authorization header to be 'Bearer', got '%s'", authorization)
1584 }
1585
1586
1587 _, err = client.Get(ts.url())
1588 if err != nil {
1589 t.Fatal(err)
1590 }
1591
1592
1593 expectedAuth = "Basic MTo="
1594 expectedConfig.ClientSecret = ""
1595 resp, err = client.Get(ts.url())
1596 if err != nil {
1597 t.Fatal(err)
1598 }
1599
1600 authorization = resp.Request.Header.Get("Authorization")
1601 if authorization != "Bearer 12345" {
1602 t.Fatalf("Expected authorization header to be 'Bearer 12345', got '%s'", authorization)
1603 }
1604
1605
1606 resp, err = client.Get(ts.url())
1607 if err != nil {
1608 t.Fatal(err)
1609 }
1610
1611
1612 expectedAuth = "Basic MToxMjM0NTY3"
1613 expectedConfig.ClientSecret = "1234567"
1614 _, err = client.Get(ts.url())
1615 if err != nil {
1616 t.Fatal(err)
1617 }
1618
1619
1620 _, err = client.Get(ts.url())
1621 if err != nil {
1622 t.Fatal(err)
1623 }
1624
1625 authorization = resp.Request.Header.Get("Authorization")
1626 if authorization != "Bearer 12345" {
1627 t.Fatalf("Expected authorization header to be 'Bearer 12345', got '%s'", authorization)
1628 }
1629 }
1630
1631 func TestOAuth2UserAgent(t *testing.T) {
1632 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1633 if r.Header.Get("User-Agent") != "myuseragent" {
1634 t.Fatalf("Expected User-Agent header in oauth request to be 'myuseragent', got '%s'", r.Header.Get("User-Agent"))
1635 }
1636
1637 res, _ := json.Marshal(oauth2TestServerResponse{
1638 AccessToken: "12345",
1639 TokenType: "Bearer",
1640 })
1641 w.Header().Add("Content-Type", "application/json")
1642 _, _ = w.Write(res)
1643 }))
1644 defer ts.Close()
1645
1646 config := DefaultHTTPClientConfig
1647 config.OAuth2 = &OAuth2{
1648 ClientID: "1",
1649 ClientSecret: "2",
1650 Scopes: []string{"A", "B"},
1651 EndpointParams: map[string]string{"hi": "hello"},
1652 TokenURL: fmt.Sprintf("%s/token", ts.URL),
1653 }
1654
1655 rt, err := NewRoundTripperFromConfig(config, "test_oauth2", WithUserAgent("myuseragent"))
1656 if err != nil {
1657 t.Fatal(err)
1658 }
1659
1660 client := http.Client{
1661 Transport: rt,
1662 }
1663 resp, err := client.Get(ts.URL)
1664 if err != nil {
1665 t.Fatal(err)
1666 }
1667
1668 authorization := resp.Request.Header.Get("Authorization")
1669 if authorization != "Bearer 12345" {
1670 t.Fatalf("Expected authorization header to be 'Bearer 12345', got '%s'", authorization)
1671 }
1672 }
1673
1674 func TestHost(t *testing.T) {
1675 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1676 if r.Host != "localhost.localdomain" {
1677 t.Fatalf("Expected Host header in request to be 'localhost.localdomain', got '%s'", r.Host)
1678 }
1679
1680 w.Header().Add("Content-Type", "application/json")
1681 }))
1682 defer ts.Close()
1683
1684 config := DefaultHTTPClientConfig
1685
1686 rt, err := NewRoundTripperFromConfig(config, "test_host", WithHost("localhost.localdomain"))
1687 if err != nil {
1688 t.Fatal(err)
1689 }
1690
1691 client := http.Client{
1692 Transport: rt,
1693 }
1694 _, err = client.Get(ts.URL)
1695 if err != nil {
1696 t.Fatal(err)
1697 }
1698 }
1699
1700 func TestOAuth2WithFile(t *testing.T) {
1701 var expectedAuth string
1702 ts := newTestOAuthServer(t, &expectedAuth)
1703 defer ts.close()
1704
1705 secretFile, err := os.CreateTemp("", "oauth2_secret")
1706 if err != nil {
1707 t.Fatal(err)
1708 }
1709 defer os.Remove(secretFile.Name())
1710
1711 yamlConfig := fmt.Sprintf(`
1712 client_id: 1
1713 client_secret_file: %s
1714 scopes:
1715 - A
1716 - B
1717 token_url: %s
1718 endpoint_params:
1719 hi: hello
1720 `, secretFile.Name(), ts.tokenURL())
1721 expectedConfig := OAuth2{
1722 ClientID: "1",
1723 ClientSecretFile: secretFile.Name(),
1724 Scopes: []string{"A", "B"},
1725 EndpointParams: map[string]string{"hi": "hello"},
1726 TokenURL: ts.tokenURL(),
1727 }
1728
1729 var unmarshalledConfig OAuth2
1730 err = yaml.Unmarshal([]byte(yamlConfig), &unmarshalledConfig)
1731 if err != nil {
1732 t.Fatalf("Expected no error unmarshalling yaml, got %v", err)
1733 }
1734 if !reflect.DeepEqual(unmarshalledConfig, expectedConfig) {
1735 t.Fatalf("Got unmarshalled config %v, expected %v", unmarshalledConfig, expectedConfig)
1736 }
1737
1738 rt := NewOAuth2RoundTripper(&expectedConfig, http.DefaultTransport, &defaultHTTPClientOptions)
1739
1740 client := http.Client{
1741 Transport: rt,
1742 }
1743
1744
1745 expectedAuth = "Basic MTo="
1746 resp, err := client.Get(ts.url())
1747 if err != nil {
1748 t.Fatal(err)
1749 }
1750
1751 authorization := resp.Request.Header.Get("Authorization")
1752 if authorization != "Bearer 12345" {
1753 t.Fatalf("Expected authorization header to be 'Bearer', got '%s'", authorization)
1754 }
1755
1756
1757 _, err = client.Get(ts.url())
1758 if err != nil {
1759 t.Fatal(err)
1760 }
1761
1762
1763 expectedAuth = "Basic MToxMjM0NTY="
1764 if _, err := secretFile.Write([]byte("123456")); err != nil {
1765 t.Fatal(err)
1766 }
1767 resp, err = client.Get(ts.url())
1768 if err != nil {
1769 t.Fatal(err)
1770 }
1771
1772 authorization = resp.Request.Header.Get("Authorization")
1773 if authorization != "Bearer 12345" {
1774 t.Fatalf("Expected authorization header to be 'Bearer 12345', got '%s'", authorization)
1775 }
1776
1777
1778 resp, err = client.Get(ts.url())
1779 if err != nil {
1780 t.Fatal(err)
1781 }
1782
1783
1784 expectedAuth = "Basic MToxMjM0NTY3"
1785 if _, err := secretFile.Write([]byte("7")); err != nil {
1786 t.Fatal(err)
1787 }
1788 _, err = client.Get(ts.url())
1789 if err != nil {
1790 t.Fatal(err)
1791 }
1792
1793
1794 _, err = client.Get(ts.url())
1795 if err != nil {
1796 t.Fatal(err)
1797 }
1798
1799 authorization = resp.Request.Header.Get("Authorization")
1800 if authorization != "Bearer 12345" {
1801 t.Fatalf("Expected authorization header to be 'Bearer 12345', got '%s'", authorization)
1802 }
1803 }
1804
1805 func TestMarshalURL(t *testing.T) {
1806 urlp, err := url.Parse("http://example.com/")
1807 if err != nil {
1808 t.Fatal(err)
1809 }
1810 u := &URL{urlp}
1811
1812 c, err := json.Marshal(u)
1813 if err != nil {
1814 t.Fatal(err)
1815 }
1816 if string(c) != "\"http://example.com/\"" {
1817 t.Fatalf("URL not properly marshaled in JSON got '%s'", string(c))
1818 }
1819
1820 c, err = yaml.Marshal(u)
1821 if err != nil {
1822 t.Fatal(err)
1823 }
1824 if string(c) != "http://example.com/\n" {
1825 t.Fatalf("URL not properly marshaled in YAML got '%s'", string(c))
1826 }
1827 }
1828
1829 func TestMarshalURLWrapperWithNilValue(t *testing.T) {
1830 u := &URL{}
1831
1832 c, err := json.Marshal(u)
1833 if err != nil {
1834 t.Fatal(err)
1835 }
1836 if string(c) != "null" {
1837 t.Fatalf("URL with nil value not properly marshaled into JSON, got %q", c)
1838 }
1839
1840 c, err = yaml.Marshal(u)
1841 if err != nil {
1842 t.Fatal(err)
1843 }
1844 if string(c) != "null\n" {
1845 t.Fatalf("URL with nil value not properly marshaled into JSON, got %q", c)
1846 }
1847 }
1848
1849 func TestUnmarshalNullURL(t *testing.T) {
1850 b := []byte(`null`)
1851
1852 {
1853 var u URL
1854 err := json.Unmarshal(b, &u)
1855 if err != nil {
1856 t.Fatal(err)
1857 }
1858 if !isEmptyNonNilURL(u.URL) {
1859 t.Fatalf("`null` literal not properly unmarshaled from JSON as URL, got %#v", u.URL)
1860 }
1861 }
1862
1863 {
1864 var u URL
1865 err := yaml.Unmarshal(b, &u)
1866 if err != nil {
1867 t.Fatal(err)
1868 }
1869 if u.URL != nil {
1870 t.Fatalf("`null` literal not properly unmarshaled from YAML as URL, got %#v", u.URL)
1871 }
1872 }
1873 }
1874
1875 func TestUnmarshalEmptyURL(t *testing.T) {
1876 b := []byte(`""`)
1877
1878 {
1879 var u URL
1880 err := json.Unmarshal(b, &u)
1881 if err != nil {
1882 t.Fatal(err)
1883 }
1884 if !isEmptyNonNilURL(u.URL) {
1885 t.Fatalf("empty string not properly unmarshaled from JSON as URL, got %#v", u.URL)
1886 }
1887 }
1888
1889 {
1890 var u URL
1891 err := yaml.Unmarshal(b, &u)
1892 if err != nil {
1893 t.Fatal(err)
1894 }
1895 if !isEmptyNonNilURL(u.URL) {
1896 t.Fatalf("empty string not properly unmarshaled from YAML as URL, got %#v", u.URL)
1897 }
1898 }
1899 }
1900
1901
1902 func isEmptyNonNilURL(u *url.URL) bool {
1903 return u != nil && *u == url.URL{}
1904 }
1905
1906 func TestUnmarshalURL(t *testing.T) {
1907 b := []byte(`"http://example.com/a b"`)
1908 var u URL
1909
1910 err := json.Unmarshal(b, &u)
1911 if err != nil {
1912 t.Fatal(err)
1913 }
1914 if u.String() != "http://example.com/a%20b" {
1915 t.Fatalf("URL not properly unmarshaled in JSON, got '%s'", u.String())
1916 }
1917
1918 err = yaml.Unmarshal(b, &u)
1919 if err != nil {
1920 t.Fatal(err)
1921 }
1922 if u.String() != "http://example.com/a%20b" {
1923 t.Fatalf("URL not properly unmarshaled in YAML, got '%s'", u.String())
1924 }
1925 }
1926
1927 func TestMarshalURLWithSecret(t *testing.T) {
1928 var u URL
1929 err := yaml.Unmarshal([]byte("http://foo:bar@example.com"), &u)
1930 if err != nil {
1931 t.Fatal(err)
1932 }
1933
1934 b, err := yaml.Marshal(u)
1935 if err != nil {
1936 t.Fatal(err)
1937 }
1938 if strings.TrimSpace(string(b)) != "http://foo:xxxxx@example.com" {
1939 t.Fatalf("URL not properly marshaled in YAML, got '%s'", string(b))
1940 }
1941 }
1942
1943 func TestOAuth2Proxy(t *testing.T) {
1944 _, _, err := LoadHTTPConfigFile("testdata/http.conf.oauth2-proxy.good.yml")
1945 if err != nil {
1946 t.Errorf("Error loading OAuth2 client config: %v", err)
1947 }
1948 }
1949
1950 func TestModifyTLSCertificates(t *testing.T) {
1951 bs := getCertificateBlobs(t)
1952
1953 tmpDir, err := os.MkdirTemp("", "modifytlscertificates")
1954 if err != nil {
1955 t.Fatal("Failed to create tmp dir", err)
1956 }
1957 defer os.RemoveAll(tmpDir)
1958 ca, cert, key := filepath.Join(tmpDir, "ca"), filepath.Join(tmpDir, "cert"), filepath.Join(tmpDir, "key")
1959
1960 handler := func(w http.ResponseWriter, r *http.Request) {
1961 fmt.Fprint(w, ExpectedMessage)
1962 }
1963 testServer, err := newTestServer(handler)
1964 if err != nil {
1965 t.Fatal(err.Error())
1966 }
1967 defer testServer.Close()
1968
1969 tests := []struct {
1970 ca string
1971 cert string
1972 key string
1973
1974 errMsg string
1975
1976 modification func()
1977 }{
1978 {
1979 ca: ClientCertificatePath,
1980 cert: ClientCertificatePath,
1981 key: ClientKeyNoPassPath,
1982
1983 errMsg: "certificate signed by unknown authority",
1984
1985 modification: func() { writeCertificate(bs, TLSCAChainPath, ca) },
1986 },
1987 {
1988 ca: TLSCAChainPath,
1989 cert: WrongClientCertPath,
1990 key: ClientKeyNoPassPath,
1991
1992 errMsg: "private key does not match public key",
1993
1994 modification: func() { writeCertificate(bs, ClientCertificatePath, cert) },
1995 },
1996 {
1997 ca: TLSCAChainPath,
1998 cert: ClientCertificatePath,
1999 key: WrongClientCertPath,
2000
2001 errMsg: "found a certificate rather than a key in the PEM for the private key",
2002
2003 modification: func() { writeCertificate(bs, ClientKeyNoPassPath, key) },
2004 },
2005 }
2006
2007 cfg := HTTPClientConfig{
2008 TLSConfig: TLSConfig{
2009 CAFile: ca,
2010 CertFile: cert,
2011 KeyFile: key,
2012 InsecureSkipVerify: false,
2013 },
2014 }
2015
2016 var c *http.Client
2017 for i, tc := range tests {
2018 t.Run(strconv.Itoa(i), func(t *testing.T) {
2019 writeCertificate(bs, tc.ca, ca)
2020 writeCertificate(bs, tc.cert, cert)
2021 writeCertificate(bs, tc.key, key)
2022 if c == nil {
2023 c, err = NewClientFromConfig(cfg, "test")
2024 if err != nil {
2025 t.Fatalf("Error creating HTTP Client: %v", err)
2026 }
2027 }
2028
2029 req, err := http.NewRequest(http.MethodGet, testServer.URL, nil)
2030 if err != nil {
2031 t.Fatalf("Error creating HTTP request: %v", err)
2032 }
2033
2034 r, err := c.Do(req)
2035 if err == nil {
2036 r.Body.Close()
2037 t.Fatalf("Could connect to the test server.")
2038 }
2039 if !strings.Contains(err.Error(), tc.errMsg) {
2040 t.Fatalf("Expected error message to contain %q, got %q", tc.errMsg, err)
2041 }
2042
2043 tc.modification()
2044
2045 r, err = c.Do(req)
2046 if err != nil {
2047 t.Fatalf("Expected no error, got %q", err)
2048 }
2049
2050 b, err := io.ReadAll(r.Body)
2051 r.Body.Close()
2052 if err != nil {
2053 t.Errorf("Can't read the server response body")
2054 }
2055
2056 got := strings.TrimSpace(string(b))
2057 if ExpectedMessage != got {
2058 t.Errorf("The expected message %q differs from the obtained message %q", ExpectedMessage, got)
2059 }
2060 })
2061 }
2062 }
2063
2064
2065 func loadHTTPConfigJSON(buf []byte) (*HTTPClientConfig, error) {
2066 cfg := &HTTPClientConfig{}
2067 err := json.Unmarshal(buf, cfg)
2068 if err != nil {
2069 return nil, err
2070 }
2071 return cfg, nil
2072 }
2073
2074
2075 func loadHTTPConfigJSONFile(filename string) (*HTTPClientConfig, []byte, error) {
2076 content, err := os.ReadFile(filename)
2077 if err != nil {
2078 return nil, nil, err
2079 }
2080 cfg, err := loadHTTPConfigJSON(content)
2081 if err != nil {
2082 return nil, nil, err
2083 }
2084 return cfg, content, nil
2085 }
2086
2087 func TestProxyConfig_Proxy(t *testing.T) {
2088 var proxyServer *httptest.Server
2089
2090 defer func() {
2091 if proxyServer != nil {
2092 proxyServer.Close()
2093 }
2094 }()
2095
2096 proxyServerHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2097 fmt.Fprintf(w, "Hello, %s", r.URL.Path)
2098 })
2099
2100 proxyServer = httptest.NewServer(proxyServerHandler)
2101
2102 testCases := []struct {
2103 name string
2104 proxyConfig string
2105 expectedProxyURL string
2106 targetURL string
2107 proxyEnv string
2108 noProxyEnv string
2109 }{
2110 {
2111 name: "proxy from environment",
2112 proxyConfig: `proxy_from_environment: true`,
2113 expectedProxyURL: proxyServer.URL,
2114 proxyEnv: proxyServer.URL,
2115 targetURL: "http://prometheus.io/",
2116 },
2117 {
2118 name: "proxy_from_environment with no_proxy",
2119 proxyConfig: `proxy_from_environment: true`,
2120 expectedProxyURL: "",
2121 proxyEnv: proxyServer.URL,
2122 noProxyEnv: "prometheus.io",
2123 targetURL: "http://prometheus.io/",
2124 },
2125 {
2126 name: "proxy_from_environment and localhost",
2127 proxyConfig: `proxy_from_environment: true`,
2128 expectedProxyURL: "",
2129 proxyEnv: proxyServer.URL,
2130 targetURL: "http://localhost/",
2131 },
2132 {
2133 name: "valid proxy_url and localhost",
2134 proxyConfig: fmt.Sprintf(`proxy_url: %s`, proxyServer.URL),
2135 expectedProxyURL: proxyServer.URL,
2136 targetURL: "http://localhost/",
2137 },
2138 {
2139 name: "valid proxy_url and no_proxy and localhost",
2140 proxyConfig: fmt.Sprintf(`proxy_url: %s
2141 no_proxy: prometheus.io`, proxyServer.URL),
2142 expectedProxyURL: "",
2143 targetURL: "http://localhost/",
2144 },
2145 {
2146 name: "valid proxy_url",
2147 proxyConfig: fmt.Sprintf(`proxy_url: %s`, proxyServer.URL),
2148 expectedProxyURL: proxyServer.URL,
2149 targetURL: "http://prometheus.io/",
2150 },
2151 {
2152 name: "valid proxy url and no_proxy",
2153 proxyConfig: fmt.Sprintf(`proxy_url: %s
2154 no_proxy: prometheus.io`, proxyServer.URL),
2155 expectedProxyURL: "",
2156 targetURL: "http://prometheus.io/",
2157 },
2158 {
2159 name: "valid proxy url and no_proxies",
2160 proxyConfig: fmt.Sprintf(`proxy_url: %s
2161 no_proxy: promcon.io,prometheus.io,cncf.io`, proxyServer.URL),
2162 expectedProxyURL: "",
2163 targetURL: "http://prometheus.io/",
2164 },
2165 {
2166 name: "valid proxy url and no_proxies that do not include target",
2167 proxyConfig: fmt.Sprintf(`proxy_url: %s
2168 no_proxy: promcon.io,cncf.io`, proxyServer.URL),
2169 expectedProxyURL: proxyServer.URL,
2170 targetURL: "http://prometheus.io/",
2171 },
2172 }
2173
2174 for _, tc := range testCases {
2175 t.Run(tc.name, func(t *testing.T) {
2176 if proxyServer != nil {
2177 defer proxyServer.Close()
2178 }
2179
2180 var proxyConfig ProxyConfig
2181
2182 err := yaml.Unmarshal([]byte(tc.proxyConfig), &proxyConfig)
2183 if err != nil {
2184 t.Errorf("failed to unmarshal proxy config: %v", err)
2185 return
2186 }
2187
2188 if tc.proxyEnv != "" {
2189 currentProxy := os.Getenv("HTTP_PROXY")
2190 t.Cleanup(func() { os.Setenv("HTTP_PROXY", currentProxy) })
2191 os.Setenv("HTTP_PROXY", tc.proxyEnv)
2192 }
2193
2194 if tc.noProxyEnv != "" {
2195 currentProxy := os.Getenv("NO_PROXY")
2196 t.Cleanup(func() { os.Setenv("NO_PROXY", currentProxy) })
2197 os.Setenv("NO_PROXY", tc.noProxyEnv)
2198 }
2199
2200 req := httptest.NewRequest("GET", tc.targetURL, nil)
2201
2202 proxyFunc := proxyConfig.Proxy()
2203 resultURL, err := proxyFunc(req)
2204 if err != nil {
2205 t.Fatalf("expected no error, but got: %v", err)
2206 return
2207 }
2208 if tc.expectedProxyURL == "" && resultURL != nil {
2209 t.Fatalf("expected no result URL, but got: %s", resultURL.String())
2210 return
2211 }
2212 if tc.expectedProxyURL != "" && resultURL == nil {
2213 t.Fatalf("expected result URL, but got nil")
2214 return
2215 }
2216 if tc.expectedProxyURL != "" && resultURL.String() != tc.expectedProxyURL {
2217 t.Fatalf("expected result URL: %s, but got: %s", tc.expectedProxyURL, resultURL.String())
2218 }
2219 })
2220 }
2221 }
2222
2223 func readFile(t *testing.T, filename string) string {
2224 t.Helper()
2225
2226 content, err := os.ReadFile(filename)
2227 if err != nil {
2228 t.Fatalf("Failed to read file %q: %s", filename, err)
2229 }
2230
2231 return string(content)
2232 }
2233
View as plain text