cache.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package transport
  14. import (
  15. "context"
  16. "fmt"
  17. "net"
  18. "net/http"
  19. "strings"
  20. "sync"
  21. "time"
  22. utilnet "k8s.io/apimachinery/pkg/util/net"
  23. "k8s.io/apimachinery/pkg/util/wait"
  24. "k8s.io/client-go/tools/metrics"
  25. )
  26. // TlsTransportCache caches TLS http.RoundTrippers different configurations. The
  27. // same RoundTripper will be returned for configs with identical TLS options If
  28. // the config has no custom TLS options, http.DefaultTransport is returned.
  29. type tlsTransportCache struct {
  30. mu sync.Mutex
  31. transports map[tlsCacheKey]*http.Transport
  32. }
  33. // DialerStopCh is stop channel that is passed down to dynamic cert dialer.
  34. // It's exposed as variable for testing purposes to avoid testing for goroutine
  35. // leakages.
  36. var DialerStopCh = wait.NeverStop
  37. const idleConnsPerHost = 25
  38. var tlsCache = &tlsTransportCache{transports: make(map[tlsCacheKey]*http.Transport)}
  39. type tlsCacheKey struct {
  40. insecure bool
  41. caData string
  42. certData string
  43. keyData string `datapolicy:"security-key"`
  44. certFile string
  45. keyFile string
  46. serverName string
  47. nextProtos string
  48. disableCompression bool
  49. // these functions are wrapped to allow them to be used as map keys
  50. getCert *GetCertHolder
  51. dial *DialHolder
  52. }
  53. func (t tlsCacheKey) String() string {
  54. keyText := "<none>"
  55. if len(t.keyData) > 0 {
  56. keyText = "<redacted>"
  57. }
  58. return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, serverName:%s, disableCompression:%t, getCert:%p, dial:%p",
  59. t.insecure, t.caData, t.certData, keyText, t.serverName, t.disableCompression, t.getCert, t.dial)
  60. }
  61. func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
  62. key, canCache, err := tlsConfigKey(config)
  63. if err != nil {
  64. return nil, err
  65. }
  66. if canCache {
  67. // Ensure we only create a single transport for the given TLS options
  68. c.mu.Lock()
  69. defer c.mu.Unlock()
  70. defer metrics.TransportCacheEntries.Observe(len(c.transports))
  71. // See if we already have a custom transport for this config
  72. if t, ok := c.transports[key]; ok {
  73. metrics.TransportCreateCalls.Increment("hit")
  74. return t, nil
  75. }
  76. metrics.TransportCreateCalls.Increment("miss")
  77. } else {
  78. metrics.TransportCreateCalls.Increment("uncacheable")
  79. }
  80. // Get the TLS options for this client config
  81. tlsConfig, err := TLSConfigFor(config)
  82. if err != nil {
  83. return nil, err
  84. }
  85. // The options didn't require a custom TLS config
  86. if tlsConfig == nil && config.DialHolder == nil && config.Proxy == nil {
  87. return http.DefaultTransport, nil
  88. }
  89. var dial func(ctx context.Context, network, address string) (net.Conn, error)
  90. if config.DialHolder != nil {
  91. dial = config.DialHolder.Dial
  92. } else {
  93. dial = (&net.Dialer{
  94. Timeout: 30 * time.Second,
  95. KeepAlive: 30 * time.Second,
  96. }).DialContext
  97. }
  98. // If we use are reloading files, we need to handle certificate rotation properly
  99. // TODO(jackkleeman): We can also add rotation here when config.HasCertCallback() is true
  100. if config.TLS.ReloadTLSFiles && tlsConfig != nil && tlsConfig.GetClientCertificate != nil {
  101. dynamicCertDialer := certRotatingDialer(tlsConfig.GetClientCertificate, dial)
  102. tlsConfig.GetClientCertificate = dynamicCertDialer.GetClientCertificate
  103. dial = dynamicCertDialer.connDialer.DialContext
  104. go dynamicCertDialer.Run(DialerStopCh)
  105. }
  106. proxy := http.ProxyFromEnvironment
  107. if config.Proxy != nil {
  108. proxy = config.Proxy
  109. }
  110. transport := utilnet.SetTransportDefaults(&http.Transport{
  111. Proxy: proxy,
  112. TLSHandshakeTimeout: 10 * time.Second,
  113. TLSClientConfig: tlsConfig,
  114. MaxIdleConnsPerHost: idleConnsPerHost,
  115. DialContext: dial,
  116. DisableCompression: config.DisableCompression,
  117. })
  118. if canCache {
  119. // Cache a single transport for these options
  120. c.transports[key] = transport
  121. }
  122. return transport, nil
  123. }
  124. // tlsConfigKey returns a unique key for tls.Config objects returned from TLSConfigFor
  125. func tlsConfigKey(c *Config) (tlsCacheKey, bool, error) {
  126. // Make sure ca/key/cert content is loaded
  127. if err := loadTLSFiles(c); err != nil {
  128. return tlsCacheKey{}, false, err
  129. }
  130. if c.Proxy != nil {
  131. // cannot determine equality for functions
  132. return tlsCacheKey{}, false, nil
  133. }
  134. k := tlsCacheKey{
  135. insecure: c.TLS.Insecure,
  136. caData: string(c.TLS.CAData),
  137. serverName: c.TLS.ServerName,
  138. nextProtos: strings.Join(c.TLS.NextProtos, ","),
  139. disableCompression: c.DisableCompression,
  140. getCert: c.TLS.GetCertHolder,
  141. dial: c.DialHolder,
  142. }
  143. if c.TLS.ReloadTLSFiles {
  144. k.certFile = c.TLS.CertFile
  145. k.keyFile = c.TLS.KeyFile
  146. } else {
  147. k.certData = string(c.TLS.CertData)
  148. k.keyData = string(c.TLS.KeyData)
  149. }
  150. return k, true, nil
  151. }