| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- package storage
- import (
- "crypto/tls"
- "crypto/x509"
- "fmt"
- "net"
- "net/http"
- "os"
- "time"
- )
- // HTTPConfig configures HTTP client settings that can be used across different storage implementations.
- type HTTPConfig struct {
- IdleConnTimeout time.Duration `yaml:"idle_conn_timeout"`
- ResponseHeaderTimeout time.Duration `yaml:"response_header_timeout"`
- InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
- TLSHandshakeTimeout time.Duration `yaml:"tls_handshake_timeout"`
- ExpectContinueTimeout time.Duration `yaml:"expect_continue_timeout"`
- MaxIdleConns int `yaml:"max_idle_conns"`
- MaxIdleConnsPerHost int `yaml:"max_idle_conns_per_host"`
- MaxConnsPerHost int `yaml:"max_conns_per_host"`
- DisableCompression bool `yaml:"disable_compression"`
- // Allow upstream callers to inject a round tripper
- Transport http.RoundTripper `yaml:"-"`
- TLSConfig TLSConfig `yaml:"tls_config"`
- }
- // NewHTTPTransport creates a new http.Transport from the HTTPConfig.
- func (config HTTPConfig) GetHTTPTransport() (http.RoundTripper, error) {
- // check for injected round tripper
- if config.Transport != nil {
- return config.Transport, nil
- }
- tlsConfig, err := config.TLSConfig.ToConfig()
- if err != nil {
- return nil, fmt.Errorf("error creating TLS config: %w", err)
- }
- if config.InsecureSkipVerify {
- tlsConfig.InsecureSkipVerify = true
- }
- return &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- DialContext: (&net.Dialer{
- Timeout: 30 * time.Second,
- KeepAlive: 30 * time.Second,
- DualStack: true,
- }).DialContext,
- MaxIdleConns: config.MaxIdleConns,
- MaxIdleConnsPerHost: config.MaxIdleConnsPerHost,
- IdleConnTimeout: config.IdleConnTimeout,
- MaxConnsPerHost: config.MaxConnsPerHost,
- TLSHandshakeTimeout: config.TLSHandshakeTimeout,
- ExpectContinueTimeout: config.ExpectContinueTimeout,
- // A custom ResponseHeaderTimeout was introduced
- // to cover cases where the tcp connection works but
- // the server never answers. Defaults to 2 minutes.
- ResponseHeaderTimeout: config.ResponseHeaderTimeout,
- // Set this value so that the underlying transport round-tripper
- // doesn't try to auto decode the body of objects with
- // content-encoding set to `gzip`.
- //
- // Refer: https://golang.org/src/net/http/transport.go?h=roundTrip#L1843.
- DisableCompression: config.DisableCompression,
- // #nosec It's up to the user to decide on TLS configs
- TLSClientConfig: tlsConfig,
- }, nil
- }
- // NewTLSConfig creates a new tls.Config from the given TLSConfig.
- func (cfg TLSConfig) ToConfig() (*tls.Config, error) {
- tlsConfig := &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify}
- // If a CA cert is provided then let's read it in.
- if len(cfg.CAFile) > 0 {
- b, err := readCAFile(cfg.CAFile)
- if err != nil {
- return nil, err
- }
- if !updateRootCA(tlsConfig, b) {
- return nil, fmt.Errorf("unable to use specified CA cert %s", cfg.CAFile)
- }
- }
- if len(cfg.ServerName) > 0 {
- tlsConfig.ServerName = cfg.ServerName
- }
- // If a client cert & key is provided then configure TLS config accordingly.
- if len(cfg.CertFile) > 0 && len(cfg.KeyFile) == 0 {
- return nil, fmt.Errorf("client cert file %q specified without client key file", cfg.CertFile)
- } else if len(cfg.KeyFile) > 0 && len(cfg.CertFile) == 0 {
- return nil, fmt.Errorf("client key file %q specified without client cert file", cfg.KeyFile)
- } else if len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 {
- // Verify that client cert and key are valid.
- if _, err := cfg.getClientCertificate(nil); err != nil {
- return nil, err
- }
- tlsConfig.GetClientCertificate = cfg.getClientCertificate
- }
- return tlsConfig, nil
- }
- // readCAFile reads the CA cert file from disk.
- func readCAFile(f string) ([]byte, error) {
- data, err := os.ReadFile(f)
- if err != nil {
- return nil, fmt.Errorf("unable to load specified CA cert %s: %s", f, err)
- }
- return data, nil
- }
- // updateRootCA parses the given byte slice as a series of PEM encoded certificates and updates tls.Config.RootCAs.
- func updateRootCA(cfg *tls.Config, b []byte) bool {
- caCertPool := x509.NewCertPool()
- if !caCertPool.AppendCertsFromPEM(b) {
- return false
- }
- cfg.RootCAs = caCertPool
- return true
- }
- // getClientCertificate reads the pair of client cert and key from disk and returns a tls.Certificate.
- func (c TLSConfig) getClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
- cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
- if err != nil {
- return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", c.CertFile, c.KeyFile, err)
- }
- return &cert, nil
- }
- // TLSConfig configures the options for TLS connections.
- type TLSConfig struct {
- // The CA cert to use for the targets.
- CAFile string `yaml:"ca_file"`
- // The client cert file for the targets.
- CertFile string `yaml:"cert_file"`
- // The client key file for the targets.
- KeyFile string `yaml:"key_file"`
- // Used to verify the hostname for the targets.
- ServerName string `yaml:"server_name"`
- // Disable target certificate validation.
- InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
- }
|