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