...

Source file src/k8s.io/client-go/transport/cache.go

Documentation: k8s.io/client-go/transport

     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  }
   179  

View as plain text