k8s.io/client-go@v0.31.1/transport/cache.go (about) 1 /* 2 Copyright 2015 The Kubernetes 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 17 package transport 18 19 import ( 20 "context" 21 "fmt" 22 "net" 23 "net/http" 24 "strings" 25 "sync" 26 "time" 27 28 utilnet "k8s.io/apimachinery/pkg/util/net" 29 "k8s.io/apimachinery/pkg/util/wait" 30 "k8s.io/client-go/tools/metrics" 31 ) 32 33 // TlsTransportCache caches TLS http.RoundTrippers different configurations. The 34 // same RoundTripper will be returned for configs with identical TLS options If 35 // the config has no custom TLS options, http.DefaultTransport is returned. 36 type tlsTransportCache struct { 37 mu sync.Mutex 38 transports map[tlsCacheKey]*http.Transport 39 } 40 41 // DialerStopCh is stop channel that is passed down to dynamic cert dialer. 42 // It's exposed as variable for testing purposes to avoid testing for goroutine 43 // leakages. 44 var DialerStopCh = wait.NeverStop 45 46 const idleConnsPerHost = 25 47 48 var tlsCache = &tlsTransportCache{transports: make(map[tlsCacheKey]*http.Transport)} 49 50 type tlsCacheKey struct { 51 insecure bool 52 caData string 53 certData string 54 keyData string `datapolicy:"security-key"` 55 certFile string 56 keyFile string 57 serverName string 58 nextProtos string 59 disableCompression bool 60 // these functions are wrapped to allow them to be used as map keys 61 getCert *GetCertHolder 62 dial *DialHolder 63 } 64 65 func (t tlsCacheKey) String() string { 66 keyText := "<none>" 67 if len(t.keyData) > 0 { 68 keyText = "<redacted>" 69 } 70 return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, serverName:%s, disableCompression:%t, getCert:%p, dial:%p", 71 t.insecure, t.caData, t.certData, keyText, t.serverName, t.disableCompression, t.getCert, t.dial) 72 } 73 74 func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) { 75 key, canCache, err := tlsConfigKey(config) 76 if err != nil { 77 return nil, err 78 } 79 80 if canCache { 81 // Ensure we only create a single transport for the given TLS options 82 c.mu.Lock() 83 defer c.mu.Unlock() 84 defer metrics.TransportCacheEntries.Observe(len(c.transports)) 85 86 // See if we already have a custom transport for this config 87 if t, ok := c.transports[key]; ok { 88 metrics.TransportCreateCalls.Increment("hit") 89 return t, nil 90 } 91 metrics.TransportCreateCalls.Increment("miss") 92 } else { 93 metrics.TransportCreateCalls.Increment("uncacheable") 94 } 95 96 // Get the TLS options for this client config 97 tlsConfig, err := TLSConfigFor(config) 98 if err != nil { 99 return nil, err 100 } 101 // The options didn't require a custom TLS config 102 if tlsConfig == nil && config.DialHolder == nil && config.Proxy == nil { 103 return http.DefaultTransport, nil 104 } 105 106 var dial func(ctx context.Context, network, address string) (net.Conn, error) 107 if config.DialHolder != nil { 108 dial = config.DialHolder.Dial 109 } else { 110 dial = (&net.Dialer{ 111 Timeout: 30 * time.Second, 112 KeepAlive: 30 * time.Second, 113 }).DialContext 114 } 115 116 // If we use are reloading files, we need to handle certificate rotation properly 117 // TODO(jackkleeman): We can also add rotation here when config.HasCertCallback() is true 118 if config.TLS.ReloadTLSFiles && tlsConfig != nil && tlsConfig.GetClientCertificate != nil { 119 dynamicCertDialer := certRotatingDialer(tlsConfig.GetClientCertificate, dial) 120 tlsConfig.GetClientCertificate = dynamicCertDialer.GetClientCertificate 121 dial = dynamicCertDialer.connDialer.DialContext 122 go dynamicCertDialer.Run(DialerStopCh) 123 } 124 125 proxy := http.ProxyFromEnvironment 126 if config.Proxy != nil { 127 proxy = config.Proxy 128 } 129 130 transport := utilnet.SetTransportDefaults(&http.Transport{ 131 Proxy: proxy, 132 TLSHandshakeTimeout: 10 * time.Second, 133 TLSClientConfig: tlsConfig, 134 MaxIdleConnsPerHost: idleConnsPerHost, 135 DialContext: dial, 136 DisableCompression: config.DisableCompression, 137 }) 138 139 if canCache { 140 // Cache a single transport for these options 141 c.transports[key] = transport 142 } 143 144 return transport, nil 145 } 146 147 // tlsConfigKey returns a unique key for tls.Config objects returned from TLSConfigFor 148 func tlsConfigKey(c *Config) (tlsCacheKey, bool, error) { 149 // Make sure ca/key/cert content is loaded 150 if err := loadTLSFiles(c); err != nil { 151 return tlsCacheKey{}, false, err 152 } 153 154 if c.Proxy != nil { 155 // cannot determine equality for functions 156 return tlsCacheKey{}, false, nil 157 } 158 159 k := tlsCacheKey{ 160 insecure: c.TLS.Insecure, 161 caData: string(c.TLS.CAData), 162 serverName: c.TLS.ServerName, 163 nextProtos: strings.Join(c.TLS.NextProtos, ","), 164 disableCompression: c.DisableCompression, 165 getCert: c.TLS.GetCertHolder, 166 dial: c.DialHolder, 167 } 168 169 if c.TLS.ReloadTLSFiles { 170 k.certFile = c.TLS.CertFile 171 k.keyFile = c.TLS.KeyFile 172 } else { 173 k.certData = string(c.TLS.CertData) 174 k.keyData = string(c.TLS.KeyData) 175 } 176 177 return k, true, nil 178 }