1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package transport
18
19import (
20	"fmt"
21	"net"
22	"net/http"
23	"strings"
24	"sync"
25	"time"
26
27	utilnet "k8s.io/apimachinery/pkg/util/net"
28)
29
30// TlsTransportCache caches TLS http.RoundTrippers different configurations. The
31// same RoundTripper will be returned for configs with identical TLS options If
32// the config has no custom TLS options, http.DefaultTransport is returned.
33type tlsTransportCache struct {
34	mu         sync.Mutex
35	transports map[tlsCacheKey]*http.Transport
36}
37
38const idleConnsPerHost = 25
39
40var tlsCache = &tlsTransportCache{transports: make(map[tlsCacheKey]*http.Transport)}
41
42type tlsCacheKey struct {
43	insecure           bool
44	caData             string
45	certData           string
46	keyData            string
47	getCert            string
48	serverName         string
49	nextProtos         string
50	dial               string
51	disableCompression bool
52}
53
54func (t tlsCacheKey) String() string {
55	keyText := "<none>"
56	if len(t.keyData) > 0 {
57		keyText = "<redacted>"
58	}
59	return fmt.Sprintf("insecure:%v, caData:%#v, certData:%#v, keyData:%s, getCert: %s, serverName:%s, dial:%s disableCompression:%t", t.insecure, t.caData, t.certData, keyText, t.getCert, t.serverName, t.dial, t.disableCompression)
60}
61
62func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
63	key, err := tlsConfigKey(config)
64	if err != nil {
65		return nil, err
66	}
67
68	// Ensure we only create a single transport for the given TLS options
69	c.mu.Lock()
70	defer c.mu.Unlock()
71
72	// See if we already have a custom transport for this config
73	if t, ok := c.transports[key]; ok {
74		return t, nil
75	}
76
77	// Get the TLS options for this client config
78	tlsConfig, err := TLSConfigFor(config)
79	if err != nil {
80		return nil, err
81	}
82	// The options didn't require a custom TLS config
83	if tlsConfig == nil && config.Dial == nil {
84		return http.DefaultTransport, nil
85	}
86
87	dial := config.Dial
88	if dial == nil {
89		dial = (&net.Dialer{
90			Timeout:   30 * time.Second,
91			KeepAlive: 30 * time.Second,
92		}).DialContext
93	}
94	// Cache a single transport for these options
95	c.transports[key] = utilnet.SetTransportDefaults(&http.Transport{
96		Proxy:               http.ProxyFromEnvironment,
97		TLSHandshakeTimeout: 10 * time.Second,
98		TLSClientConfig:     tlsConfig,
99		MaxIdleConnsPerHost: idleConnsPerHost,
100		DialContext:         dial,
101		DisableCompression:  config.DisableCompression,
102	})
103	return c.transports[key], nil
104}
105
106// tlsConfigKey returns a unique key for tls.Config objects returned from TLSConfigFor
107func tlsConfigKey(c *Config) (tlsCacheKey, error) {
108	// Make sure ca/key/cert content is loaded
109	if err := loadTLSFiles(c); err != nil {
110		return tlsCacheKey{}, err
111	}
112	return tlsCacheKey{
113		insecure:           c.TLS.Insecure,
114		caData:             string(c.TLS.CAData),
115		certData:           string(c.TLS.CertData),
116		keyData:            string(c.TLS.KeyData),
117		getCert:            fmt.Sprintf("%p", c.TLS.GetCert),
118		serverName:         c.TLS.ServerName,
119		nextProtos:         strings.Join(c.TLS.NextProtos, ","),
120		dial:               fmt.Sprintf("%p", c.Dial),
121		disableCompression: c.DisableCompression,
122	}, nil
123}
124