blob: 88d89494d728cedeca80a1f0db8add78ef399737 [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001/*
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 "context"
21 "crypto/tls"
22 "crypto/x509"
23 "fmt"
24 "io/ioutil"
25 "net/http"
26 "sync"
27 "time"
28
29 utilnet "k8s.io/apimachinery/pkg/util/net"
30 "k8s.io/klog/v2"
31)
32
33// New returns an http.RoundTripper that will provide the authentication
34// or transport level security defined by the provided Config.
35func New(config *Config) (http.RoundTripper, error) {
36 // Set transport level security
37 if config.Transport != nil && (config.HasCA() || config.HasCertAuth() || config.HasCertCallback() || config.TLS.Insecure) {
38 return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed")
39 }
40
41 var (
42 rt http.RoundTripper
43 err error
44 )
45
46 if config.Transport != nil {
47 rt = config.Transport
48 } else {
49 rt, err = tlsCache.get(config)
50 if err != nil {
51 return nil, err
52 }
53 }
54
55 return HTTPWrappersForConfig(config, rt)
56}
57
58// TLSConfigFor returns a tls.Config that will provide the transport level security defined
59// by the provided Config. Will return nil if no transport level security is requested.
60func TLSConfigFor(c *Config) (*tls.Config, error) {
61 if !(c.HasCA() || c.HasCertAuth() || c.HasCertCallback() || c.TLS.Insecure || len(c.TLS.ServerName) > 0 || len(c.TLS.NextProtos) > 0) {
62 return nil, nil
63 }
64 if c.HasCA() && c.TLS.Insecure {
65 return nil, fmt.Errorf("specifying a root certificates file with the insecure flag is not allowed")
66 }
67 if err := loadTLSFiles(c); err != nil {
68 return nil, err
69 }
70
71 tlsConfig := &tls.Config{
72 // Can't use SSLv3 because of POODLE and BEAST
73 // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
74 // Can't use TLSv1.1 because of RC4 cipher usage
75 MinVersion: tls.VersionTLS12,
76 InsecureSkipVerify: c.TLS.Insecure,
77 ServerName: c.TLS.ServerName,
78 NextProtos: c.TLS.NextProtos,
79 }
80
81 if c.HasCA() {
82 tlsConfig.RootCAs = rootCertPool(c.TLS.CAData)
83 }
84
85 var staticCert *tls.Certificate
86 // Treat cert as static if either key or cert was data, not a file
87 if c.HasCertAuth() && !c.TLS.ReloadTLSFiles {
88 // If key/cert were provided, verify them before setting up
89 // tlsConfig.GetClientCertificate.
90 cert, err := tls.X509KeyPair(c.TLS.CertData, c.TLS.KeyData)
91 if err != nil {
92 return nil, err
93 }
94 staticCert = &cert
95 }
96
97 var dynamicCertLoader func() (*tls.Certificate, error)
98 if c.TLS.ReloadTLSFiles {
99 dynamicCertLoader = cachingCertificateLoader(c.TLS.CertFile, c.TLS.KeyFile)
100 }
101
102 if c.HasCertAuth() || c.HasCertCallback() {
103 tlsConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
104 // Note: static key/cert data always take precedence over cert
105 // callback.
106 if staticCert != nil {
107 return staticCert, nil
108 }
109 // key/cert files lead to ReloadTLSFiles being set - takes precedence over cert callback
110 if dynamicCertLoader != nil {
111 return dynamicCertLoader()
112 }
113 if c.HasCertCallback() {
114 cert, err := c.TLS.GetCert()
115 if err != nil {
116 return nil, err
117 }
118 // GetCert may return empty value, meaning no cert.
119 if cert != nil {
120 return cert, nil
121 }
122 }
123
124 // Both c.TLS.CertData/KeyData were unset and GetCert didn't return
125 // anything. Return an empty tls.Certificate, no client cert will
126 // be sent to the server.
127 return &tls.Certificate{}, nil
128 }
129 }
130
131 return tlsConfig, nil
132}
133
134// loadTLSFiles copies the data from the CertFile, KeyFile, and CAFile fields into the CertData,
135// KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are
136// either populated or were empty to start.
137func loadTLSFiles(c *Config) error {
138 var err error
139 c.TLS.CAData, err = dataFromSliceOrFile(c.TLS.CAData, c.TLS.CAFile)
140 if err != nil {
141 return err
142 }
143
144 // Check that we are purely loading from files
145 if len(c.TLS.CertFile) > 0 && len(c.TLS.CertData) == 0 && len(c.TLS.KeyFile) > 0 && len(c.TLS.KeyData) == 0 {
146 c.TLS.ReloadTLSFiles = true
147 }
148
149 c.TLS.CertData, err = dataFromSliceOrFile(c.TLS.CertData, c.TLS.CertFile)
150 if err != nil {
151 return err
152 }
153
154 c.TLS.KeyData, err = dataFromSliceOrFile(c.TLS.KeyData, c.TLS.KeyFile)
155 if err != nil {
156 return err
157 }
158 return nil
159}
160
161// dataFromSliceOrFile returns data from the slice (if non-empty), or from the file,
162// or an error if an error occurred reading the file
163func dataFromSliceOrFile(data []byte, file string) ([]byte, error) {
164 if len(data) > 0 {
165 return data, nil
166 }
167 if len(file) > 0 {
168 fileData, err := ioutil.ReadFile(file)
169 if err != nil {
170 return []byte{}, err
171 }
172 return fileData, nil
173 }
174 return nil, nil
175}
176
177// rootCertPool returns nil if caData is empty. When passed along, this will mean "use system CAs".
178// When caData is not empty, it will be the ONLY information used in the CertPool.
179func rootCertPool(caData []byte) *x509.CertPool {
180 // What we really want is a copy of x509.systemRootsPool, but that isn't exposed. It's difficult to build (see the go
181 // code for a look at the platform specific insanity), so we'll use the fact that RootCAs == nil gives us the system values
182 // It doesn't allow trusting either/or, but hopefully that won't be an issue
183 if len(caData) == 0 {
184 return nil
185 }
186
187 // if we have caData, use it
188 certPool := x509.NewCertPool()
189 certPool.AppendCertsFromPEM(caData)
190 return certPool
191}
192
193// WrapperFunc wraps an http.RoundTripper when a new transport
194// is created for a client, allowing per connection behavior
195// to be injected.
196type WrapperFunc func(rt http.RoundTripper) http.RoundTripper
197
198// Wrappers accepts any number of wrappers and returns a wrapper
199// function that is the equivalent of calling each of them in order. Nil
200// values are ignored, which makes this function convenient for incrementally
201// wrapping a function.
202func Wrappers(fns ...WrapperFunc) WrapperFunc {
203 if len(fns) == 0 {
204 return nil
205 }
206 // optimize the common case of wrapping a possibly nil transport wrapper
207 // with an additional wrapper
208 if len(fns) == 2 && fns[0] == nil {
209 return fns[1]
210 }
211 return func(rt http.RoundTripper) http.RoundTripper {
212 base := rt
213 for _, fn := range fns {
214 if fn != nil {
215 base = fn(base)
216 }
217 }
218 return base
219 }
220}
221
222// ContextCanceller prevents new requests after the provided context is finished.
223// err is returned when the context is closed, allowing the caller to provide a context
224// appropriate error.
225func ContextCanceller(ctx context.Context, err error) WrapperFunc {
226 return func(rt http.RoundTripper) http.RoundTripper {
227 return &contextCanceller{
228 ctx: ctx,
229 rt: rt,
230 err: err,
231 }
232 }
233}
234
235type contextCanceller struct {
236 ctx context.Context
237 rt http.RoundTripper
238 err error
239}
240
241func (b *contextCanceller) RoundTrip(req *http.Request) (*http.Response, error) {
242 select {
243 case <-b.ctx.Done():
244 return nil, b.err
245 default:
246 return b.rt.RoundTrip(req)
247 }
248}
249
250func tryCancelRequest(rt http.RoundTripper, req *http.Request) {
251 type canceler interface {
252 CancelRequest(*http.Request)
253 }
254 switch rt := rt.(type) {
255 case canceler:
256 rt.CancelRequest(req)
257 case utilnet.RoundTripperWrapper:
258 tryCancelRequest(rt.WrappedRoundTripper(), req)
259 default:
260 klog.Warningf("Unable to cancel request for %T", rt)
261 }
262}
263
264type certificateCacheEntry struct {
265 cert *tls.Certificate
266 err error
267 birth time.Time
268}
269
270// isStale returns true when this cache entry is too old to be usable
271func (c *certificateCacheEntry) isStale() bool {
272 return time.Now().Sub(c.birth) > time.Second
273}
274
275func newCertificateCacheEntry(certFile, keyFile string) certificateCacheEntry {
276 cert, err := tls.LoadX509KeyPair(certFile, keyFile)
277 return certificateCacheEntry{cert: &cert, err: err, birth: time.Now()}
278}
279
280// cachingCertificateLoader ensures that we don't hammer the filesystem when opening many connections
281// the underlying cert files are read at most once every second
282func cachingCertificateLoader(certFile, keyFile string) func() (*tls.Certificate, error) {
283 current := newCertificateCacheEntry(certFile, keyFile)
284 var currentMtx sync.RWMutex
285
286 return func() (*tls.Certificate, error) {
287 currentMtx.RLock()
288 if current.isStale() {
289 currentMtx.RUnlock()
290
291 currentMtx.Lock()
292 defer currentMtx.Unlock()
293
294 if current.isStale() {
295 current = newCertificateCacheEntry(certFile, keyFile)
296 }
297 } else {
298 defer currentMtx.RUnlock()
299 }
300
301 return current.cert, current.err
302 }
303}