...

Source file src/github.com/prometheus/common/config/http_config_test.go

Documentation: github.com/prometheus/common/config

     1  // Copyright 2015 The Prometheus Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    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  			// We don't have access to the test server's URL when configuring the test cases,
   514  			// so it has to be specified here.
   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  	// Normal flow.
   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  	// Should honor already Authorization header set.
   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  	// Normal flow.
   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  	// Should honor already Authorization header set.
   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  	// tlsConfig.rootCAs.LazyCerts contains functions getCert() in go 1.16, which are
   812  	// never equal. Compare the Subjects instead.
   813  	//nolint:staticcheck // Ignore SA1019. (*CertPool).Subjects is deprecated because it may not include the system certs but it isn't the case here.
   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  	// Non-nil functions are never equal.
   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  			// Valid certs.
  1084  			ca:   TLSCAChainPath,
  1085  			cert: ClientCertificatePath,
  1086  			key:  ClientKeyNoPassPath,
  1087  		},
  1088  		{
  1089  			// CA not matching.
  1090  			ca:   ClientCertificatePath,
  1091  			cert: ClientCertificatePath,
  1092  			key:  ClientKeyNoPassPath,
  1093  
  1094  			errMsg: "certificate signed by unknown authority",
  1095  		},
  1096  		{
  1097  			// Invalid client cert+key.
  1098  			ca:   TLSCAChainPath,
  1099  			cert: WrongClientCertPath,
  1100  			key:  WrongClientKeyPath,
  1101  
  1102  			errMsg: "remote error: tls",
  1103  		},
  1104  		{
  1105  			// CA file empty
  1106  			ca:   EmptyFile,
  1107  			cert: ClientCertificatePath,
  1108  			key:  ClientKeyNoPassPath,
  1109  
  1110  			errMsg: "unable to use specified CA cert",
  1111  		},
  1112  		{
  1113  			// cert file empty
  1114  			ca:   TLSCAChainPath,
  1115  			cert: EmptyFile,
  1116  			key:  ClientKeyNoPassPath,
  1117  
  1118  			errMsg: "failed to find any PEM data in certificate input",
  1119  		},
  1120  		{
  1121  			// key file empty
  1122  			ca:   TLSCAChainPath,
  1123  			cert: ClientCertificatePath,
  1124  			key:  EmptyFile,
  1125  
  1126  			errMsg: "failed to find any PEM data in key input",
  1127  		},
  1128  		{
  1129  			// Valid certs again.
  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  			// File-based everything.
  1212  			caFile:   TLSCAChainPath,
  1213  			certFile: ClientCertificatePath,
  1214  			keyFile:  ClientKeyNoPassPath,
  1215  		},
  1216  		{
  1217  			// Inline CA.
  1218  			caText:   readFile(t, TLSCAChainPath),
  1219  			certFile: ClientCertificatePath,
  1220  			keyFile:  ClientKeyNoPassPath,
  1221  		},
  1222  		{
  1223  			// Inline cert.
  1224  			caFile:   TLSCAChainPath,
  1225  			certText: readFile(t, ClientCertificatePath),
  1226  			keyFile:  ClientKeyNoPassPath,
  1227  		},
  1228  		{
  1229  			// Inline key.
  1230  			caFile:   TLSCAChainPath,
  1231  			certFile: ClientCertificatePath,
  1232  			keyText:  readFile(t, ClientKeyNoPassPath),
  1233  		},
  1234  		{
  1235  			// Inline everything.
  1236  			caText:   readFile(t, TLSCAChainPath),
  1237  			certText: readFile(t, ClientCertificatePath),
  1238  			keyText:  readFile(t, ClientKeyNoPassPath),
  1239  		},
  1240  
  1241  		{
  1242  			// Invalid inline CA.
  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  			// Invalid cert.
  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  			// Invalid key.
  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  	// Spawn 10 Go routines polling the server concurrently.
  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  	// Change the CA file every 10ms for 1 second.
  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  	// String method must not reveal authentication credentials.
  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  // NewRoundTripCheckRequest creates a new instance of a type that implements http.RoundTripper,
  1471  // which before returning theResponse and theError, executes checkRequest against a http.Request.
  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  // newTestOAuthServer returns a new test server with the expected base64 encoded client ID and secret.
  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  	// Default secret.
  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  	// Making a second request with the same secret should not re-call the token API.
  1587  	_, err = client.Get(ts.url())
  1588  	if err != nil {
  1589  		t.Fatal(err)
  1590  	}
  1591  
  1592  	// Empty secret.
  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  	// Making a second request with the same secret should not re-call the token API.
  1606  	resp, err = client.Get(ts.url())
  1607  	if err != nil {
  1608  		t.Fatal(err)
  1609  	}
  1610  
  1611  	// Update secret.
  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  	// Making a second request with the same secret should not re-call the token API.
  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  	// Empty secret file.
  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  	// Making a second request with the same file content should not re-call the token API.
  1757  	_, err = client.Get(ts.url())
  1758  	if err != nil {
  1759  		t.Fatal(err)
  1760  	}
  1761  
  1762  	// File populated.
  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  	// Making a second request with the same file content should not re-call the token API.
  1778  	resp, err = client.Get(ts.url())
  1779  	if err != nil {
  1780  		t.Fatal(err)
  1781  	}
  1782  
  1783  	// Update file.
  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  	// Making a second request with the same file content should not re-call the token API.
  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 { // UnmarshalYAML is not called when parsing null literal.
  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  // checks if u equals to &url.URL{}
  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  // loadHTTPConfigJSON parses the JSON input s into a HTTPClientConfig.
  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  // loadHTTPConfigJSONFile parses the given JSON file into a HTTPClientConfig.
  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