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