http.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package storage
  2. import (
  3. "crypto/tls"
  4. "crypto/x509"
  5. "fmt"
  6. "net"
  7. "net/http"
  8. "os"
  9. "time"
  10. )
  11. // HTTPConfig configures HTTP client settings that can be used across different storage implementations.
  12. type HTTPConfig struct {
  13. IdleConnTimeout time.Duration `yaml:"idle_conn_timeout"`
  14. ResponseHeaderTimeout time.Duration `yaml:"response_header_timeout"`
  15. InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
  16. TLSHandshakeTimeout time.Duration `yaml:"tls_handshake_timeout"`
  17. ExpectContinueTimeout time.Duration `yaml:"expect_continue_timeout"`
  18. MaxIdleConns int `yaml:"max_idle_conns"`
  19. MaxIdleConnsPerHost int `yaml:"max_idle_conns_per_host"`
  20. MaxConnsPerHost int `yaml:"max_conns_per_host"`
  21. DisableCompression bool `yaml:"disable_compression"`
  22. // Allow upstream callers to inject a round tripper
  23. Transport http.RoundTripper `yaml:"-"`
  24. TLSConfig TLSConfig `yaml:"tls_config"`
  25. }
  26. // NewHTTPTransport creates a new http.Transport from the HTTPConfig.
  27. func (config HTTPConfig) GetHTTPTransport() (http.RoundTripper, error) {
  28. // check for injected round tripper
  29. if config.Transport != nil {
  30. return config.Transport, nil
  31. }
  32. tlsConfig, err := config.TLSConfig.ToConfig()
  33. if err != nil {
  34. return nil, fmt.Errorf("error creating TLS config: %w", err)
  35. }
  36. if config.InsecureSkipVerify {
  37. tlsConfig.InsecureSkipVerify = true
  38. }
  39. return &http.Transport{
  40. Proxy: http.ProxyFromEnvironment,
  41. DialContext: (&net.Dialer{
  42. Timeout: 30 * time.Second,
  43. KeepAlive: 30 * time.Second,
  44. DualStack: true,
  45. }).DialContext,
  46. MaxIdleConns: config.MaxIdleConns,
  47. MaxIdleConnsPerHost: config.MaxIdleConnsPerHost,
  48. IdleConnTimeout: config.IdleConnTimeout,
  49. MaxConnsPerHost: config.MaxConnsPerHost,
  50. TLSHandshakeTimeout: config.TLSHandshakeTimeout,
  51. ExpectContinueTimeout: config.ExpectContinueTimeout,
  52. // A custom ResponseHeaderTimeout was introduced
  53. // to cover cases where the tcp connection works but
  54. // the server never answers. Defaults to 2 minutes.
  55. ResponseHeaderTimeout: config.ResponseHeaderTimeout,
  56. // Set this value so that the underlying transport round-tripper
  57. // doesn't try to auto decode the body of objects with
  58. // content-encoding set to `gzip`.
  59. //
  60. // Refer: https://golang.org/src/net/http/transport.go?h=roundTrip#L1843.
  61. DisableCompression: config.DisableCompression,
  62. // #nosec It's up to the user to decide on TLS configs
  63. TLSClientConfig: tlsConfig,
  64. }, nil
  65. }
  66. // NewTLSConfig creates a new tls.Config from the given TLSConfig.
  67. func (cfg TLSConfig) ToConfig() (*tls.Config, error) {
  68. tlsConfig := &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify}
  69. // If a CA cert is provided then let's read it in.
  70. if len(cfg.CAFile) > 0 {
  71. b, err := readCAFile(cfg.CAFile)
  72. if err != nil {
  73. return nil, err
  74. }
  75. if !updateRootCA(tlsConfig, b) {
  76. return nil, fmt.Errorf("unable to use specified CA cert %s", cfg.CAFile)
  77. }
  78. }
  79. if len(cfg.ServerName) > 0 {
  80. tlsConfig.ServerName = cfg.ServerName
  81. }
  82. // If a client cert & key is provided then configure TLS config accordingly.
  83. if len(cfg.CertFile) > 0 && len(cfg.KeyFile) == 0 {
  84. return nil, fmt.Errorf("client cert file %q specified without client key file", cfg.CertFile)
  85. } else if len(cfg.KeyFile) > 0 && len(cfg.CertFile) == 0 {
  86. return nil, fmt.Errorf("client key file %q specified without client cert file", cfg.KeyFile)
  87. } else if len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 {
  88. // Verify that client cert and key are valid.
  89. if _, err := cfg.getClientCertificate(nil); err != nil {
  90. return nil, err
  91. }
  92. tlsConfig.GetClientCertificate = cfg.getClientCertificate
  93. }
  94. return tlsConfig, nil
  95. }
  96. // readCAFile reads the CA cert file from disk.
  97. func readCAFile(f string) ([]byte, error) {
  98. data, err := os.ReadFile(f)
  99. if err != nil {
  100. return nil, fmt.Errorf("unable to load specified CA cert %s: %s", f, err)
  101. }
  102. return data, nil
  103. }
  104. // updateRootCA parses the given byte slice as a series of PEM encoded certificates and updates tls.Config.RootCAs.
  105. func updateRootCA(cfg *tls.Config, b []byte) bool {
  106. caCertPool := x509.NewCertPool()
  107. if !caCertPool.AppendCertsFromPEM(b) {
  108. return false
  109. }
  110. cfg.RootCAs = caCertPool
  111. return true
  112. }
  113. // getClientCertificate reads the pair of client cert and key from disk and returns a tls.Certificate.
  114. func (c TLSConfig) getClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
  115. cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
  116. if err != nil {
  117. return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", c.CertFile, c.KeyFile, err)
  118. }
  119. return &cert, nil
  120. }
  121. // TLSConfig configures the options for TLS connections.
  122. type TLSConfig struct {
  123. // The CA cert to use for the targets.
  124. CAFile string `yaml:"ca_file"`
  125. // The client cert file for the targets.
  126. CertFile string `yaml:"cert_file"`
  127. // The client key file for the targets.
  128. KeyFile string `yaml:"key_file"`
  129. // Used to verify the hostname for the targets.
  130. ServerName string `yaml:"server_name"`
  131. // Disable target certificate validation.
  132. InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
  133. }