| /* |
| 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 ( |
| "context" |
| "crypto/tls" |
| "crypto/x509" |
| "fmt" |
| "io/ioutil" |
| "net/http" |
| "sync" |
| "time" |
| |
| utilnet "k8s.io/apimachinery/pkg/util/net" |
| "k8s.io/klog/v2" |
| ) |
| |
| // New returns an http.RoundTripper that will provide the authentication |
| // or transport level security defined by the provided Config. |
| func New(config *Config) (http.RoundTripper, error) { |
| // Set transport level security |
| if config.Transport != nil && (config.HasCA() || config.HasCertAuth() || config.HasCertCallback() || config.TLS.Insecure) { |
| return nil, fmt.Errorf("using a custom transport with TLS certificate options or the insecure flag is not allowed") |
| } |
| |
| var ( |
| rt http.RoundTripper |
| err error |
| ) |
| |
| if config.Transport != nil { |
| rt = config.Transport |
| } else { |
| rt, err = tlsCache.get(config) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| return HTTPWrappersForConfig(config, rt) |
| } |
| |
| // TLSConfigFor returns a tls.Config that will provide the transport level security defined |
| // by the provided Config. Will return nil if no transport level security is requested. |
| func TLSConfigFor(c *Config) (*tls.Config, error) { |
| if !(c.HasCA() || c.HasCertAuth() || c.HasCertCallback() || c.TLS.Insecure || len(c.TLS.ServerName) > 0 || len(c.TLS.NextProtos) > 0) { |
| return nil, nil |
| } |
| if c.HasCA() && c.TLS.Insecure { |
| return nil, fmt.Errorf("specifying a root certificates file with the insecure flag is not allowed") |
| } |
| if err := loadTLSFiles(c); err != nil { |
| return nil, err |
| } |
| |
| tlsConfig := &tls.Config{ |
| // Can't use SSLv3 because of POODLE and BEAST |
| // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher |
| // Can't use TLSv1.1 because of RC4 cipher usage |
| MinVersion: tls.VersionTLS12, |
| InsecureSkipVerify: c.TLS.Insecure, |
| ServerName: c.TLS.ServerName, |
| NextProtos: c.TLS.NextProtos, |
| } |
| |
| if c.HasCA() { |
| tlsConfig.RootCAs = rootCertPool(c.TLS.CAData) |
| } |
| |
| var staticCert *tls.Certificate |
| // Treat cert as static if either key or cert was data, not a file |
| if c.HasCertAuth() && !c.TLS.ReloadTLSFiles { |
| // If key/cert were provided, verify them before setting up |
| // tlsConfig.GetClientCertificate. |
| cert, err := tls.X509KeyPair(c.TLS.CertData, c.TLS.KeyData) |
| if err != nil { |
| return nil, err |
| } |
| staticCert = &cert |
| } |
| |
| var dynamicCertLoader func() (*tls.Certificate, error) |
| if c.TLS.ReloadTLSFiles { |
| dynamicCertLoader = cachingCertificateLoader(c.TLS.CertFile, c.TLS.KeyFile) |
| } |
| |
| if c.HasCertAuth() || c.HasCertCallback() { |
| tlsConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { |
| // Note: static key/cert data always take precedence over cert |
| // callback. |
| if staticCert != nil { |
| return staticCert, nil |
| } |
| // key/cert files lead to ReloadTLSFiles being set - takes precedence over cert callback |
| if dynamicCertLoader != nil { |
| return dynamicCertLoader() |
| } |
| if c.HasCertCallback() { |
| cert, err := c.TLS.GetCert() |
| if err != nil { |
| return nil, err |
| } |
| // GetCert may return empty value, meaning no cert. |
| if cert != nil { |
| return cert, nil |
| } |
| } |
| |
| // Both c.TLS.CertData/KeyData were unset and GetCert didn't return |
| // anything. Return an empty tls.Certificate, no client cert will |
| // be sent to the server. |
| return &tls.Certificate{}, nil |
| } |
| } |
| |
| return tlsConfig, nil |
| } |
| |
| // loadTLSFiles copies the data from the CertFile, KeyFile, and CAFile fields into the CertData, |
| // KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are |
| // either populated or were empty to start. |
| func loadTLSFiles(c *Config) error { |
| var err error |
| c.TLS.CAData, err = dataFromSliceOrFile(c.TLS.CAData, c.TLS.CAFile) |
| if err != nil { |
| return err |
| } |
| |
| // Check that we are purely loading from files |
| if len(c.TLS.CertFile) > 0 && len(c.TLS.CertData) == 0 && len(c.TLS.KeyFile) > 0 && len(c.TLS.KeyData) == 0 { |
| c.TLS.ReloadTLSFiles = true |
| } |
| |
| c.TLS.CertData, err = dataFromSliceOrFile(c.TLS.CertData, c.TLS.CertFile) |
| if err != nil { |
| return err |
| } |
| |
| c.TLS.KeyData, err = dataFromSliceOrFile(c.TLS.KeyData, c.TLS.KeyFile) |
| if err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // dataFromSliceOrFile returns data from the slice (if non-empty), or from the file, |
| // or an error if an error occurred reading the file |
| func dataFromSliceOrFile(data []byte, file string) ([]byte, error) { |
| if len(data) > 0 { |
| return data, nil |
| } |
| if len(file) > 0 { |
| fileData, err := ioutil.ReadFile(file) |
| if err != nil { |
| return []byte{}, err |
| } |
| return fileData, nil |
| } |
| return nil, nil |
| } |
| |
| // rootCertPool returns nil if caData is empty. When passed along, this will mean "use system CAs". |
| // When caData is not empty, it will be the ONLY information used in the CertPool. |
| func rootCertPool(caData []byte) *x509.CertPool { |
| // What we really want is a copy of x509.systemRootsPool, but that isn't exposed. It's difficult to build (see the go |
| // code for a look at the platform specific insanity), so we'll use the fact that RootCAs == nil gives us the system values |
| // It doesn't allow trusting either/or, but hopefully that won't be an issue |
| if len(caData) == 0 { |
| return nil |
| } |
| |
| // if we have caData, use it |
| certPool := x509.NewCertPool() |
| certPool.AppendCertsFromPEM(caData) |
| return certPool |
| } |
| |
| // WrapperFunc wraps an http.RoundTripper when a new transport |
| // is created for a client, allowing per connection behavior |
| // to be injected. |
| type WrapperFunc func(rt http.RoundTripper) http.RoundTripper |
| |
| // Wrappers accepts any number of wrappers and returns a wrapper |
| // function that is the equivalent of calling each of them in order. Nil |
| // values are ignored, which makes this function convenient for incrementally |
| // wrapping a function. |
| func Wrappers(fns ...WrapperFunc) WrapperFunc { |
| if len(fns) == 0 { |
| return nil |
| } |
| // optimize the common case of wrapping a possibly nil transport wrapper |
| // with an additional wrapper |
| if len(fns) == 2 && fns[0] == nil { |
| return fns[1] |
| } |
| return func(rt http.RoundTripper) http.RoundTripper { |
| base := rt |
| for _, fn := range fns { |
| if fn != nil { |
| base = fn(base) |
| } |
| } |
| return base |
| } |
| } |
| |
| // ContextCanceller prevents new requests after the provided context is finished. |
| // err is returned when the context is closed, allowing the caller to provide a context |
| // appropriate error. |
| func ContextCanceller(ctx context.Context, err error) WrapperFunc { |
| return func(rt http.RoundTripper) http.RoundTripper { |
| return &contextCanceller{ |
| ctx: ctx, |
| rt: rt, |
| err: err, |
| } |
| } |
| } |
| |
| type contextCanceller struct { |
| ctx context.Context |
| rt http.RoundTripper |
| err error |
| } |
| |
| func (b *contextCanceller) RoundTrip(req *http.Request) (*http.Response, error) { |
| select { |
| case <-b.ctx.Done(): |
| return nil, b.err |
| default: |
| return b.rt.RoundTrip(req) |
| } |
| } |
| |
| func tryCancelRequest(rt http.RoundTripper, req *http.Request) { |
| type canceler interface { |
| CancelRequest(*http.Request) |
| } |
| switch rt := rt.(type) { |
| case canceler: |
| rt.CancelRequest(req) |
| case utilnet.RoundTripperWrapper: |
| tryCancelRequest(rt.WrappedRoundTripper(), req) |
| default: |
| klog.Warningf("Unable to cancel request for %T", rt) |
| } |
| } |
| |
| type certificateCacheEntry struct { |
| cert *tls.Certificate |
| err error |
| birth time.Time |
| } |
| |
| // isStale returns true when this cache entry is too old to be usable |
| func (c *certificateCacheEntry) isStale() bool { |
| return time.Now().Sub(c.birth) > time.Second |
| } |
| |
| func newCertificateCacheEntry(certFile, keyFile string) certificateCacheEntry { |
| cert, err := tls.LoadX509KeyPair(certFile, keyFile) |
| return certificateCacheEntry{cert: &cert, err: err, birth: time.Now()} |
| } |
| |
| // cachingCertificateLoader ensures that we don't hammer the filesystem when opening many connections |
| // the underlying cert files are read at most once every second |
| func cachingCertificateLoader(certFile, keyFile string) func() (*tls.Certificate, error) { |
| current := newCertificateCacheEntry(certFile, keyFile) |
| var currentMtx sync.RWMutex |
| |
| return func() (*tls.Certificate, error) { |
| currentMtx.RLock() |
| if current.isStale() { |
| currentMtx.RUnlock() |
| |
| currentMtx.Lock() |
| defer currentMtx.Unlock() |
| |
| if current.isStale() { |
| current = newCertificateCacheEntry(certFile, keyFile) |
| } |
| } else { |
| defer currentMtx.RUnlock() |
| } |
| |
| return current.cert, current.err |
| } |
| } |