github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/config/config_http.go (about)

     1  // Copyright 2016 The Prometheus Authors
     2  // Copyright 2021 The Pyroscope Authors
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  // http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  //go:build go1.8
    17  // +build go1.8
    18  
    19  package config
    20  
    21  import (
    22  	"bytes"
    23  	"context"
    24  	"crypto/sha256"
    25  	"crypto/tls"
    26  	"crypto/x509"
    27  	"encoding/json"
    28  	"fmt"
    29  	"net"
    30  	"net/http"
    31  	"net/url"
    32  	"os"
    33  	"path/filepath"
    34  	"strings"
    35  	"sync"
    36  	"time"
    37  
    38  	"github.com/mwitkow/go-conntrack"
    39  	"golang.org/x/net/http2"
    40  	"golang.org/x/oauth2"
    41  	"golang.org/x/oauth2/clientcredentials"
    42  	"gopkg.in/yaml.v2"
    43  )
    44  
    45  // revive:disable:max-public-structs complex domain
    46  
    47  // DefaultHTTPClientConfig is the default HTTP client configuration.
    48  var DefaultHTTPClientConfig = HTTPClientConfig{
    49  	FollowRedirects: true,
    50  }
    51  
    52  // defaultHTTPClientOptions holds the default HTTP client options.
    53  var defaultHTTPClientOptions = httpClientOptions{
    54  	keepAlivesEnabled: true,
    55  	http2Enabled:      true,
    56  	// 5 minutes is typically above the maximum sane scrape interval. So we can
    57  	// use keepalive for all configurations.
    58  	idleConnTimeout: 5 * time.Minute,
    59  }
    60  
    61  type closeIdler interface {
    62  	CloseIdleConnections()
    63  }
    64  
    65  // BasicAuth contains basic HTTP authentication credentials.
    66  type BasicAuth struct {
    67  	Username     string `yaml:"username" json:"username"`
    68  	Password     Secret `yaml:"password,omitempty" json:"password,omitempty"`
    69  	PasswordFile string `yaml:"password-file,omitempty" json:"password-file,omitempty"`
    70  }
    71  
    72  // SetDirectory joins any relative file paths with dir.
    73  func (a *BasicAuth) SetDirectory(dir string) {
    74  	if a == nil {
    75  		return
    76  	}
    77  	a.PasswordFile = JoinDir(dir, a.PasswordFile)
    78  }
    79  
    80  // Authorization contains HTTP authorization credentials.
    81  type Authorization struct {
    82  	Type            string `yaml:"type,omitempty" json:"type,omitempty"`
    83  	Credentials     Secret `yaml:"credentials,omitempty" json:"credentials,omitempty"`
    84  	CredentialsFile string `yaml:"credentials-file,omitempty" json:"credentials-file,omitempty"`
    85  }
    86  
    87  // SetDirectory joins any relative file paths with dir.
    88  func (a *Authorization) SetDirectory(dir string) {
    89  	if a == nil {
    90  		return
    91  	}
    92  	a.CredentialsFile = JoinDir(dir, a.CredentialsFile)
    93  }
    94  
    95  // URL is a custom URL type that allows validation at configuration load time.
    96  type URL struct {
    97  	*url.URL
    98  }
    99  
   100  // UnmarshalYAML implements the yaml.Unmarshaler interface for URLs.
   101  func (u *URL) UnmarshalYAML(unmarshal func(interface{}) error) error {
   102  	var s string
   103  	if err := unmarshal(&s); err != nil {
   104  		return err
   105  	}
   106  
   107  	urlp, err := url.Parse(s)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	u.URL = urlp
   112  	return nil
   113  }
   114  
   115  // MarshalYAML implements the yaml.Marshaler interface for URLs.
   116  func (u URL) MarshalYAML() (interface{}, error) {
   117  	if u.URL != nil {
   118  		return u.Redacted(), nil
   119  	}
   120  	return nil, nil
   121  }
   122  
   123  // Redacted returns the URL but replaces any password with "xxxxx".
   124  func (u URL) Redacted() string {
   125  	if u.URL == nil {
   126  		return ""
   127  	}
   128  
   129  	ru := *u.URL
   130  	if _, ok := ru.User.Password(); ok {
   131  		// We can not use secretToken because it would be escaped.
   132  		ru.User = url.UserPassword(ru.User.Username(), "xxxxx")
   133  	}
   134  	return ru.String()
   135  }
   136  
   137  // UnmarshalJSON implements the json.Marshaler interface for URL.
   138  func (u *URL) UnmarshalJSON(data []byte) error {
   139  	var s string
   140  	if err := json.Unmarshal(data, &s); err != nil {
   141  		return err
   142  	}
   143  	urlp, err := url.Parse(s)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	u.URL = urlp
   148  	return nil
   149  }
   150  
   151  // MarshalJSON implements the json.Marshaler interface for URL.
   152  func (u URL) MarshalJSON() ([]byte, error) {
   153  	if u.URL != nil {
   154  		return json.Marshal(u.URL.String())
   155  	}
   156  	return []byte("null"), nil
   157  }
   158  
   159  // OAuth2 is the oauth2 client configuration.
   160  type OAuth2 struct {
   161  	ClientID         string            `yaml:"client-id" json:"client-id"`
   162  	ClientSecret     Secret            `yaml:"client-secret" json:"client-secret"`
   163  	ClientSecretFile string            `yaml:"client-secret-file" json:"client-secret-file"`
   164  	Scopes           []string          `yaml:"scopes,omitempty" json:"scopes,omitempty"`
   165  	TokenURL         string            `yaml:"token-url" json:"token-url"`
   166  	EndpointParams   map[string]string `yaml:"endpoint-params,omitempty" json:"endpoint-params,omitempty"`
   167  
   168  	// TLSConfig is used to connect to the token URL.
   169  	TLSConfig TLSConfig `yaml:"tls-config,omitempty"`
   170  }
   171  
   172  // SetDirectory joins any relative file paths with dir.
   173  func (a *OAuth2) SetDirectory(dir string) {
   174  	if a == nil {
   175  		return
   176  	}
   177  	a.ClientSecretFile = JoinDir(dir, a.ClientSecretFile)
   178  	a.TLSConfig.SetDirectory(dir)
   179  }
   180  
   181  // HTTPClientConfig configures an HTTP client.
   182  type HTTPClientConfig struct {
   183  	// The HTTP basic authentication credentials for the targets.
   184  	BasicAuth *BasicAuth `yaml:"basic-auth,omitempty" json:"basic-auth,omitempty"`
   185  	// The HTTP authorization credentials for the targets.
   186  	Authorization *Authorization `yaml:"authorization,omitempty" json:"authorization,omitempty"`
   187  	// The OAuth2 client credentials used to fetch a token for the targets.
   188  	OAuth2 *OAuth2 `yaml:"oauth2,omitempty" json:"oauth2,omitempty"`
   189  	// The bearer token for the targets. Deprecated in favour of
   190  	// Authorization.Credentials.
   191  	BearerToken Secret `yaml:"bearer-token,omitempty" json:"bearer-token,omitempty"`
   192  	// The bearer token file for the targets. Deprecated in favour of
   193  	// Authorization.CredentialsFile.
   194  	BearerTokenFile string `yaml:"bearer-token-file,omitempty" json:"bearer-token-file,omitempty"`
   195  	// HTTP proxy server to use to connect to the targets.
   196  	ProxyURL URL `yaml:"proxy-url,omitempty" json:"proxy-url,omitempty"`
   197  	// TLSConfig to use to connect to the targets.
   198  	TLSConfig TLSConfig `yaml:"tls-config,omitempty" json:"tls-config,omitempty"`
   199  	// FollowRedirects specifies whether the client should follow HTTP 3xx redirects.
   200  	// The omitempty flag is not set, because it would be hidden from the
   201  	// marshalled configuration when set to false.
   202  	FollowRedirects bool `yaml:"follow-redirects" json:"follow-redirects"`
   203  }
   204  
   205  // SetDirectory joins any relative file paths with dir.
   206  func (c *HTTPClientConfig) SetDirectory(dir string) {
   207  	if c == nil {
   208  		return
   209  	}
   210  	c.TLSConfig.SetDirectory(dir)
   211  	c.BasicAuth.SetDirectory(dir)
   212  	c.Authorization.SetDirectory(dir)
   213  	c.OAuth2.SetDirectory(dir)
   214  	c.BearerTokenFile = JoinDir(dir, c.BearerTokenFile)
   215  }
   216  
   217  // Validate validates the HTTPClientConfig to check only one of BearerToken,
   218  // BasicAuth and BearerTokenFile is configured.
   219  func (c *HTTPClientConfig) Validate() error {
   220  	// Backwards compatibility with the bearer-token field.
   221  	if len(c.BearerToken) > 0 && len(c.BearerTokenFile) > 0 {
   222  		return fmt.Errorf("at most one of bearer-token & bearer-token-file must be configured")
   223  	}
   224  	if (c.BasicAuth != nil || c.OAuth2 != nil) && (len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0) {
   225  		return fmt.Errorf("at most one of basic-auth, oauth2, bearer-token & bearer-token-file must be configured")
   226  	}
   227  	if c.BasicAuth != nil && (string(c.BasicAuth.Password) != "" && c.BasicAuth.PasswordFile != "") {
   228  		return fmt.Errorf("at most one of basic-auth password & password-file must be configured")
   229  	}
   230  	if c.Authorization != nil {
   231  		if len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0 {
   232  			return fmt.Errorf("authorization is not compatible with bearer-token & bearer-token-file")
   233  		}
   234  		if string(c.Authorization.Credentials) != "" && c.Authorization.CredentialsFile != "" {
   235  			return fmt.Errorf("at most one of authorization credentials & credentials-file must be configured")
   236  		}
   237  		c.Authorization.Type = strings.TrimSpace(c.Authorization.Type)
   238  		if len(c.Authorization.Type) == 0 {
   239  			c.Authorization.Type = "Bearer"
   240  		}
   241  		if strings.ToLower(c.Authorization.Type) == "basic" {
   242  			return fmt.Errorf(`authorization type cannot be set to "basic", use "basic-auth" instead`)
   243  		}
   244  		if c.BasicAuth != nil || c.OAuth2 != nil {
   245  			return fmt.Errorf("at most one of basic-auth, oauth2 & authorization must be configured")
   246  		}
   247  	} else {
   248  		if len(c.BearerToken) > 0 {
   249  			c.Authorization = &Authorization{Credentials: c.BearerToken}
   250  			c.Authorization.Type = "Bearer"
   251  			c.BearerToken = ""
   252  		}
   253  		if len(c.BearerTokenFile) > 0 {
   254  			c.Authorization = &Authorization{CredentialsFile: c.BearerTokenFile}
   255  			c.Authorization.Type = "Bearer"
   256  			c.BearerTokenFile = ""
   257  		}
   258  	}
   259  	if c.OAuth2 != nil {
   260  		if c.BasicAuth != nil {
   261  			return fmt.Errorf("at most one of basic-auth, oauth2 & authorization must be configured")
   262  		}
   263  		if len(c.OAuth2.ClientID) == 0 {
   264  			return fmt.Errorf("oauth2 client-id must be configured")
   265  		}
   266  		if len(c.OAuth2.ClientSecret) == 0 && len(c.OAuth2.ClientSecretFile) == 0 {
   267  			return fmt.Errorf("either oauth2 client-secret or client-secret-file must be configured")
   268  		}
   269  		if len(c.OAuth2.TokenURL) == 0 {
   270  			return fmt.Errorf("oauth2 token-url must be configured")
   271  		}
   272  		if len(c.OAuth2.ClientSecret) > 0 && len(c.OAuth2.ClientSecretFile) > 0 {
   273  			return fmt.Errorf("at most one of oauth2 client-secret & client-secret-file must be configured")
   274  		}
   275  	}
   276  	return nil
   277  }
   278  
   279  // UnmarshalYAML implements the yaml.Unmarshaler interface
   280  func (c *HTTPClientConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
   281  	type plain HTTPClientConfig
   282  	*c = DefaultHTTPClientConfig
   283  	if err := unmarshal((*plain)(c)); err != nil {
   284  		return err
   285  	}
   286  	return c.Validate()
   287  }
   288  
   289  // UnmarshalJSON implements the json.Marshaler interface for URL.
   290  func (c *HTTPClientConfig) UnmarshalJSON(data []byte) error {
   291  	type plain HTTPClientConfig
   292  	*c = DefaultHTTPClientConfig
   293  	if err := json.Unmarshal(data, (*plain)(c)); err != nil {
   294  		return err
   295  	}
   296  	return c.Validate()
   297  }
   298  
   299  // UnmarshalYAML implements the yaml.Unmarshaler interface.
   300  func (a *BasicAuth) UnmarshalYAML(unmarshal func(interface{}) error) error {
   301  	type plain BasicAuth
   302  	return unmarshal((*plain)(a))
   303  }
   304  
   305  // DialContextFunc defines the signature of the DialContext() function implemented
   306  // by net.Dialer.
   307  type DialContextFunc func(context.Context, string, string) (net.Conn, error)
   308  
   309  type httpClientOptions struct {
   310  	dialContextFunc   DialContextFunc
   311  	keepAlivesEnabled bool
   312  	http2Enabled      bool
   313  	idleConnTimeout   time.Duration
   314  }
   315  
   316  // HTTPClientOption defines an option that can be applied to the HTTP client.
   317  type HTTPClientOption func(options *httpClientOptions)
   318  
   319  // WithDialContextFunc allows you to override func gets used for the actual dialing. The default is `net.Dialer.DialContext`.
   320  func WithDialContextFunc(fn DialContextFunc) HTTPClientOption {
   321  	return func(opts *httpClientOptions) {
   322  		opts.dialContextFunc = fn
   323  	}
   324  }
   325  
   326  // WithKeepAlivesDisabled allows to disable HTTP keepalive.
   327  func WithKeepAlivesDisabled() HTTPClientOption {
   328  	return func(opts *httpClientOptions) {
   329  		opts.keepAlivesEnabled = false
   330  	}
   331  }
   332  
   333  // WithHTTP2Disabled allows to disable HTTP2.
   334  func WithHTTP2Disabled() HTTPClientOption {
   335  	return func(opts *httpClientOptions) {
   336  		opts.http2Enabled = false
   337  	}
   338  }
   339  
   340  // WithIdleConnTimeout allows setting the idle connection timeout.
   341  func WithIdleConnTimeout(timeout time.Duration) HTTPClientOption {
   342  	return func(opts *httpClientOptions) {
   343  		opts.idleConnTimeout = timeout
   344  	}
   345  }
   346  
   347  // newClient returns a http.Client using the specified http.RoundTripper.
   348  func newClient(rt http.RoundTripper) *http.Client {
   349  	return &http.Client{Transport: rt}
   350  }
   351  
   352  // NewClientFromConfig returns a new HTTP client configured for the
   353  // given config.HTTPClientConfig and config.HTTPClientOption.
   354  // The name is used as go-conntrack metric label.
   355  func NewClientFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HTTPClientOption) (*http.Client, error) {
   356  	rt, err := NewRoundTripperFromConfig(cfg, name, optFuncs...)
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  	client := newClient(rt)
   361  	if !cfg.FollowRedirects {
   362  		client.CheckRedirect = func(*http.Request, []*http.Request) error {
   363  			return http.ErrUseLastResponse
   364  		}
   365  	}
   366  	return client, nil
   367  }
   368  
   369  // NewRoundTripperFromConfig returns a new HTTP RoundTripper configured for the
   370  // given config.HTTPClientConfig and config.HTTPClientOption.
   371  // The name is used as go-conntrack metric label.
   372  func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HTTPClientOption) (http.RoundTripper, error) {
   373  	opts := defaultHTTPClientOptions
   374  	for _, f := range optFuncs {
   375  		f(&opts)
   376  	}
   377  
   378  	var dialContext func(ctx context.Context, network, addr string) (net.Conn, error)
   379  
   380  	if opts.dialContextFunc != nil {
   381  		dialContext = conntrack.NewDialContextFunc(
   382  			conntrack.DialWithDialContextFunc((func(context.Context, string, string) (net.Conn, error))(opts.dialContextFunc)),
   383  			conntrack.DialWithTracing(),
   384  			conntrack.DialWithName(name))
   385  	} else {
   386  		dialContext = conntrack.NewDialContextFunc(
   387  			conntrack.DialWithTracing(),
   388  			conntrack.DialWithName(name))
   389  	}
   390  
   391  	newRT := func(tlsConfig *tls.Config) (http.RoundTripper, error) {
   392  		// The only timeout we care about is the configured scrape timeout.
   393  		// It is applied on request. So we leave out any timings here.
   394  		var rt http.RoundTripper = &http.Transport{
   395  			Proxy:                 http.ProxyURL(cfg.ProxyURL.URL),
   396  			MaxIdleConns:          20000,
   397  			MaxIdleConnsPerHost:   1000, // see https://github.com/golang/go/issues/13801
   398  			DisableKeepAlives:     !opts.keepAlivesEnabled,
   399  			TLSClientConfig:       tlsConfig,
   400  			DisableCompression:    true,
   401  			IdleConnTimeout:       opts.idleConnTimeout,
   402  			TLSHandshakeTimeout:   10 * time.Second,
   403  			ExpectContinueTimeout: 1 * time.Second,
   404  			DialContext:           dialContext,
   405  		}
   406  		if opts.http2Enabled && os.Getenv("PYROSCOPE_DISABLE_HTTP2") == "" {
   407  			// HTTP/2 support is golang had many problematic cornercases where
   408  			// dead connections would be kept and used in connection pools.
   409  			// https://github.com/golang/go/issues/32388
   410  			// https://github.com/golang/go/issues/39337
   411  			// https://github.com/golang/go/issues/39750
   412  
   413  			// Do not enable HTTP2 if the environment variable
   414  			// PYROSCOPE_DISABLE_HTTP2 is set to a non-empty value.
   415  			// This allows users to easily disable HTTP2 in case they run into
   416  			// issues again, but will be removed once we are confident that
   417  			// things work as expected.
   418  
   419  			http2t, err := http2.ConfigureTransports(rt.(*http.Transport))
   420  			if err != nil {
   421  				return nil, err
   422  			}
   423  			http2t.ReadIdleTimeout = time.Minute
   424  		}
   425  
   426  		// If a authorization-credentials is provided, create a round tripper that will set the
   427  		// Authorization header correctly on each request.
   428  		if cfg.Authorization != nil && len(cfg.Authorization.Credentials) > 0 {
   429  			rt = NewAuthorizationCredentialsRoundTripper(cfg.Authorization.Type, cfg.Authorization.Credentials, rt)
   430  		} else if cfg.Authorization != nil && len(cfg.Authorization.CredentialsFile) > 0 {
   431  			rt = NewAuthorizationCredentialsFileRoundTripper(cfg.Authorization.Type, cfg.Authorization.CredentialsFile, rt)
   432  		}
   433  		// Backwards compatibility, be nice with importers who would not have
   434  		// called Validate().
   435  		if len(cfg.BearerToken) > 0 {
   436  			rt = NewAuthorizationCredentialsRoundTripper("Bearer", cfg.BearerToken, rt)
   437  		} else if len(cfg.BearerTokenFile) > 0 {
   438  			rt = NewAuthorizationCredentialsFileRoundTripper("Bearer", cfg.BearerTokenFile, rt)
   439  		}
   440  
   441  		if cfg.BasicAuth != nil {
   442  			rt = NewBasicAuthRoundTripper(cfg.BasicAuth.Username, cfg.BasicAuth.Password, cfg.BasicAuth.PasswordFile, rt)
   443  		}
   444  
   445  		if cfg.OAuth2 != nil {
   446  			rt = NewOAuth2RoundTripper(cfg.OAuth2, rt)
   447  		}
   448  		// Return a new configured RoundTripper.
   449  		return rt, nil
   450  	}
   451  
   452  	tlsConfig, err := NewTLSConfig(&cfg.TLSConfig)
   453  	if err != nil {
   454  		return nil, err
   455  	}
   456  
   457  	if len(cfg.TLSConfig.CAFile) == 0 {
   458  		// No need for a RoundTripper that reloads the CA file automatically.
   459  		return newRT(tlsConfig)
   460  	}
   461  
   462  	return NewTLSRoundTripper(tlsConfig, cfg.TLSConfig.CAFile, newRT)
   463  }
   464  
   465  type authorizationCredentialsRoundTripper struct {
   466  	authType        string
   467  	authCredentials Secret
   468  	rt              http.RoundTripper
   469  }
   470  
   471  // NewAuthorizationCredentialsRoundTripper adds the provided credentials to a
   472  // request unless the authorization header has already been set.
   473  func NewAuthorizationCredentialsRoundTripper(authType string, authCredentials Secret, rt http.RoundTripper) http.RoundTripper {
   474  	return &authorizationCredentialsRoundTripper{authType, authCredentials, rt}
   475  }
   476  
   477  func (rt *authorizationCredentialsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   478  	if len(req.Header.Get("Authorization")) == 0 {
   479  		req = cloneRequest(req)
   480  		req.Header.Set("Authorization", fmt.Sprintf("%s %s", rt.authType, string(rt.authCredentials)))
   481  	}
   482  	return rt.rt.RoundTrip(req)
   483  }
   484  
   485  func (rt *authorizationCredentialsRoundTripper) CloseIdleConnections() {
   486  	if ci, ok := rt.rt.(closeIdler); ok {
   487  		ci.CloseIdleConnections()
   488  	}
   489  }
   490  
   491  type authorizationCredentialsFileRoundTripper struct {
   492  	authType            string
   493  	authCredentialsFile string
   494  	rt                  http.RoundTripper
   495  }
   496  
   497  // NewAuthorizationCredentialsFileRoundTripper adds the authorization
   498  // credentials read from the provided file to a request unless the authorization
   499  // header has already been set. This file is read for every request.
   500  func NewAuthorizationCredentialsFileRoundTripper(authType, authCredentialsFile string, rt http.RoundTripper) http.RoundTripper {
   501  	return &authorizationCredentialsFileRoundTripper{authType, authCredentialsFile, rt}
   502  }
   503  
   504  func (rt *authorizationCredentialsFileRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   505  	if len(req.Header.Get("Authorization")) == 0 {
   506  		b, err := os.ReadFile(rt.authCredentialsFile)
   507  		if err != nil {
   508  			return nil, fmt.Errorf("unable to read authorization credentials file %s: %s", rt.authCredentialsFile, err)
   509  		}
   510  		authCredentials := strings.TrimSpace(string(b))
   511  
   512  		req = cloneRequest(req)
   513  		req.Header.Set("Authorization", fmt.Sprintf("%s %s", rt.authType, authCredentials))
   514  	}
   515  
   516  	return rt.rt.RoundTrip(req)
   517  }
   518  
   519  func (rt *authorizationCredentialsFileRoundTripper) CloseIdleConnections() {
   520  	if ci, ok := rt.rt.(closeIdler); ok {
   521  		ci.CloseIdleConnections()
   522  	}
   523  }
   524  
   525  type basicAuthRoundTripper struct {
   526  	username     string
   527  	password     Secret
   528  	passwordFile string
   529  	rt           http.RoundTripper
   530  }
   531  
   532  // NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a request unless it has
   533  // already been set.
   534  func NewBasicAuthRoundTripper(username string, password Secret, passwordFile string, rt http.RoundTripper) http.RoundTripper {
   535  	return &basicAuthRoundTripper{username, password, passwordFile, rt}
   536  }
   537  
   538  func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   539  	if len(req.Header.Get("Authorization")) != 0 {
   540  		return rt.rt.RoundTrip(req)
   541  	}
   542  	req = cloneRequest(req)
   543  	if rt.passwordFile != "" {
   544  		bs, err := os.ReadFile(rt.passwordFile)
   545  		if err != nil {
   546  			return nil, fmt.Errorf("unable to read basic auth password file %s: %s", rt.passwordFile, err)
   547  		}
   548  		req.SetBasicAuth(rt.username, strings.TrimSpace(string(bs)))
   549  	} else {
   550  		req.SetBasicAuth(rt.username, strings.TrimSpace(string(rt.password)))
   551  	}
   552  	return rt.rt.RoundTrip(req)
   553  }
   554  
   555  func (rt *basicAuthRoundTripper) CloseIdleConnections() {
   556  	if ci, ok := rt.rt.(closeIdler); ok {
   557  		ci.CloseIdleConnections()
   558  	}
   559  }
   560  
   561  type oauth2RoundTripper struct {
   562  	config *OAuth2
   563  	rt     http.RoundTripper
   564  	next   http.RoundTripper
   565  	secret string
   566  	mtx    sync.RWMutex
   567  }
   568  
   569  func NewOAuth2RoundTripper(config *OAuth2, next http.RoundTripper) http.RoundTripper {
   570  	return &oauth2RoundTripper{
   571  		config: config,
   572  		next:   next,
   573  	}
   574  }
   575  
   576  func (rt *oauth2RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   577  	var (
   578  		secret  string
   579  		changed bool
   580  	)
   581  
   582  	if rt.config.ClientSecretFile != "" {
   583  		data, err := os.ReadFile(rt.config.ClientSecretFile)
   584  		if err != nil {
   585  			return nil, fmt.Errorf("unable to read oauth2 client secret file %s: %s", rt.config.ClientSecretFile, err)
   586  		}
   587  		secret = strings.TrimSpace(string(data))
   588  		rt.mtx.RLock()
   589  		changed = secret != rt.secret
   590  		rt.mtx.RUnlock()
   591  	}
   592  
   593  	if changed || rt.rt == nil {
   594  		if rt.config.ClientSecret != "" {
   595  			secret = string(rt.config.ClientSecret)
   596  		}
   597  
   598  		config := &clientcredentials.Config{
   599  			ClientID:       rt.config.ClientID,
   600  			ClientSecret:   secret,
   601  			Scopes:         rt.config.Scopes,
   602  			TokenURL:       rt.config.TokenURL,
   603  			EndpointParams: mapToValues(rt.config.EndpointParams),
   604  		}
   605  
   606  		tlsConfig, err := NewTLSConfig(&rt.config.TLSConfig)
   607  		if err != nil {
   608  			return nil, err
   609  		}
   610  
   611  		var t http.RoundTripper
   612  		if len(rt.config.TLSConfig.CAFile) == 0 {
   613  			t = &http.Transport{TLSClientConfig: tlsConfig}
   614  		} else {
   615  			t, err = NewTLSRoundTripper(tlsConfig, rt.config.TLSConfig.CAFile, func(tls *tls.Config) (http.RoundTripper, error) {
   616  				return &http.Transport{TLSClientConfig: tls}, nil
   617  			})
   618  			if err != nil {
   619  				return nil, err
   620  			}
   621  		}
   622  
   623  		ctx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{Transport: t})
   624  		tokenSource := config.TokenSource(ctx)
   625  
   626  		rt.mtx.Lock()
   627  		rt.secret = secret
   628  		rt.rt = &oauth2.Transport{
   629  			Base:   rt.next,
   630  			Source: tokenSource,
   631  		}
   632  		rt.mtx.Unlock()
   633  	}
   634  
   635  	rt.mtx.RLock()
   636  	currentRT := rt.rt
   637  	rt.mtx.RUnlock()
   638  	return currentRT.RoundTrip(req)
   639  }
   640  
   641  func (rt *oauth2RoundTripper) CloseIdleConnections() {
   642  	// OAuth2 RT does not support CloseIdleConnections() but the next RT might.
   643  	if ci, ok := rt.next.(closeIdler); ok {
   644  		ci.CloseIdleConnections()
   645  	}
   646  }
   647  
   648  func mapToValues(m map[string]string) url.Values {
   649  	v := url.Values{}
   650  	for name, value := range m {
   651  		v.Set(name, value)
   652  	}
   653  
   654  	return v
   655  }
   656  
   657  // cloneRequest returns a clone of the provided *http.Request.
   658  // The clone is a shallow copy of the struct and its Header map.
   659  func cloneRequest(r *http.Request) *http.Request {
   660  	// Shallow copy of the struct.
   661  	r2 := new(http.Request)
   662  	*r2 = *r
   663  	// Deep copy of the Header.
   664  	r2.Header = make(http.Header)
   665  	for k, s := range r.Header {
   666  		r2.Header[k] = s
   667  	}
   668  	return r2
   669  }
   670  
   671  // NewTLSConfig creates a new tls.Config from the given TLSConfig.
   672  func NewTLSConfig(cfg *TLSConfig) (*tls.Config, error) {
   673  	tlsConfig := &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify}
   674  
   675  	// If a CA cert is provided then let's read it in so we can validate the
   676  	// scrape target's certificate properly.
   677  	if len(cfg.CAFile) > 0 {
   678  		b, err := readCAFile(cfg.CAFile)
   679  		if err != nil {
   680  			return nil, err
   681  		}
   682  		if !updateRootCA(tlsConfig, b) {
   683  			return nil, fmt.Errorf("unable to use specified CA cert %s", cfg.CAFile)
   684  		}
   685  	}
   686  
   687  	if len(cfg.ServerName) > 0 {
   688  		tlsConfig.ServerName = cfg.ServerName
   689  	}
   690  	// If a client cert & key is provided then configure TLS config accordingly.
   691  	if len(cfg.CertFile) > 0 && len(cfg.KeyFile) == 0 {
   692  		return nil, fmt.Errorf("client cert file %q specified without client key file", cfg.CertFile)
   693  	} else if len(cfg.KeyFile) > 0 && len(cfg.CertFile) == 0 {
   694  		return nil, fmt.Errorf("client key file %q specified without client cert file", cfg.KeyFile)
   695  	} else if len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 {
   696  		// Verify that client cert and key are valid.
   697  		if _, err := cfg.getClientCertificate(nil); err != nil {
   698  			return nil, err
   699  		}
   700  		tlsConfig.GetClientCertificate = cfg.getClientCertificate
   701  	}
   702  
   703  	return tlsConfig, nil
   704  }
   705  
   706  // TLSConfig configures the options for TLS connections.
   707  type TLSConfig struct {
   708  	// The CA cert to use for the targets.
   709  	CAFile string `yaml:"ca-file,omitempty" json:"ca-file,omitempty"`
   710  	// The client cert file for the targets.
   711  	CertFile string `yaml:"cert-file,omitempty" json:"cert-file,omitempty"`
   712  	// The client key file for the targets.
   713  	KeyFile string `yaml:"key-file,omitempty" json:"key-file,omitempty"`
   714  	// Used to verify the hostname for the targets.
   715  	ServerName string `yaml:"server-name,omitempty" json:"server-name,omitempty"`
   716  	// Disable target certificate validation.
   717  	InsecureSkipVerify bool `yaml:"insecure-skip-verify" json:"insecure-skip-verify"`
   718  }
   719  
   720  // SetDirectory joins any relative file paths with dir.
   721  func (c *TLSConfig) SetDirectory(dir string) {
   722  	if c == nil {
   723  		return
   724  	}
   725  	c.CAFile = JoinDir(dir, c.CAFile)
   726  	c.CertFile = JoinDir(dir, c.CertFile)
   727  	c.KeyFile = JoinDir(dir, c.KeyFile)
   728  }
   729  
   730  // UnmarshalYAML implements the yaml.Unmarshaler interface.
   731  func (c *TLSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
   732  	type plain TLSConfig
   733  	return unmarshal((*plain)(c))
   734  }
   735  
   736  // getClientCertificate reads the pair of client cert and key from disk and returns a tls.Certificate.
   737  func (c *TLSConfig) getClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
   738  	cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
   739  	if err != nil {
   740  		return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", c.CertFile, c.KeyFile, err)
   741  	}
   742  	return &cert, nil
   743  }
   744  
   745  // readCAFile reads the CA cert file from disk.
   746  func readCAFile(f string) ([]byte, error) {
   747  	data, err := os.ReadFile(f)
   748  	if err != nil {
   749  		return nil, fmt.Errorf("unable to load specified CA cert %s: %s", f, err)
   750  	}
   751  	return data, nil
   752  }
   753  
   754  // updateRootCA parses the given byte slice as a series of PEM encoded certificates and updates tls.Config.RootCAs.
   755  func updateRootCA(cfg *tls.Config, b []byte) bool {
   756  	caCertPool := x509.NewCertPool()
   757  	if !caCertPool.AppendCertsFromPEM(b) {
   758  		return false
   759  	}
   760  	cfg.RootCAs = caCertPool
   761  	return true
   762  }
   763  
   764  // tlsRoundTripper is a RoundTripper that updates automatically its TLS
   765  // configuration whenever the content of the CA file changes.
   766  type tlsRoundTripper struct {
   767  	caFile string
   768  	// newRT returns a new RoundTripper.
   769  	newRT func(*tls.Config) (http.RoundTripper, error)
   770  
   771  	mtx        sync.RWMutex
   772  	rt         http.RoundTripper
   773  	hashCAFile []byte
   774  	tlsConfig  *tls.Config
   775  }
   776  
   777  func NewTLSRoundTripper(
   778  	cfg *tls.Config,
   779  	caFile string,
   780  	newRT func(*tls.Config) (http.RoundTripper, error),
   781  ) (http.RoundTripper, error) {
   782  	t := &tlsRoundTripper{
   783  		caFile:    caFile,
   784  		newRT:     newRT,
   785  		tlsConfig: cfg,
   786  	}
   787  
   788  	rt, err := t.newRT(t.tlsConfig)
   789  	if err != nil {
   790  		return nil, err
   791  	}
   792  	t.rt = rt
   793  	_, t.hashCAFile, err = t.getCAWithHash()
   794  	if err != nil {
   795  		return nil, err
   796  	}
   797  
   798  	return t, nil
   799  }
   800  
   801  func (t *tlsRoundTripper) getCAWithHash() ([]byte, []byte, error) {
   802  	b, err := readCAFile(t.caFile)
   803  	if err != nil {
   804  		return nil, nil, err
   805  	}
   806  	h := sha256.Sum256(b)
   807  	return b, h[:], nil
   808  }
   809  
   810  // RoundTrip implements the http.RoundTrip interface.
   811  func (t *tlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   812  	b, h, err := t.getCAWithHash()
   813  	if err != nil {
   814  		return nil, err
   815  	}
   816  
   817  	t.mtx.RLock()
   818  	equal := bytes.Equal(h[:], t.hashCAFile)
   819  	rt := t.rt
   820  	t.mtx.RUnlock()
   821  	if equal {
   822  		// The CA cert hasn't changed, use the existing RoundTripper.
   823  		return rt.RoundTrip(req)
   824  	}
   825  
   826  	// Create a new RoundTripper.
   827  	tlsConfig := t.tlsConfig.Clone()
   828  	if !updateRootCA(tlsConfig, b) {
   829  		return nil, fmt.Errorf("unable to use specified CA cert %s", t.caFile)
   830  	}
   831  	rt, err = t.newRT(tlsConfig)
   832  	if err != nil {
   833  		return nil, err
   834  	}
   835  	t.CloseIdleConnections()
   836  
   837  	t.mtx.Lock()
   838  	t.rt = rt
   839  	t.hashCAFile = h[:]
   840  	t.mtx.Unlock()
   841  
   842  	return rt.RoundTrip(req)
   843  }
   844  
   845  func (t *tlsRoundTripper) CloseIdleConnections() {
   846  	t.mtx.RLock()
   847  	defer t.mtx.RUnlock()
   848  	if ci, ok := t.rt.(closeIdler); ok {
   849  		ci.CloseIdleConnections()
   850  	}
   851  }
   852  
   853  func (c HTTPClientConfig) String() string {
   854  	b, err := yaml.Marshal(c)
   855  	if err != nil {
   856  		return fmt.Sprintf("<error creating http client config string: %s>", err)
   857  	}
   858  	return string(b)
   859  }
   860  
   861  const secretToken = "<secret>"
   862  
   863  // Secret special type for storing secrets.
   864  type Secret string
   865  
   866  // MarshalYAML implements the yaml.Marshaler interface for Secrets.
   867  func (s Secret) MarshalYAML() (interface{}, error) {
   868  	if s != "" {
   869  		return secretToken, nil
   870  	}
   871  	return nil, nil
   872  }
   873  
   874  // UnmarshalYAML implements the yaml.Unmarshaler interface for Secrets.
   875  func (s *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error {
   876  	type plain Secret
   877  	return unmarshal((*plain)(s))
   878  }
   879  
   880  // MarshalJSON implements the json.Marshaler interface for Secret.
   881  func (s Secret) MarshalJSON() ([]byte, error) {
   882  	if len(s) == 0 {
   883  		return json.Marshal("")
   884  	}
   885  	return json.Marshal(secretToken)
   886  }
   887  
   888  // DirectorySetter is a config type that contains file paths that may
   889  // be relative to the file containing the config.
   890  type DirectorySetter interface {
   891  	// SetDirectory joins any relative file paths with dir.
   892  	// Any paths that are empty or absolute remain unchanged.
   893  	SetDirectory(dir string)
   894  }
   895  
   896  // JoinDir joins dir and path if path is relative.
   897  // If path is empty or absolute, it is returned unchanged.
   898  func JoinDir(dir, path string) string {
   899  	if path == "" || filepath.IsAbs(path) {
   900  		return path
   901  	}
   902  	return filepath.Join(dir, path)
   903  }