k8s.io/kubernetes@v1.29.3/pkg/kubelet/certificate/transport.go (about) 1 /* 2 Copyright 2017 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 certificate 18 19 import ( 20 "crypto/tls" 21 "fmt" 22 "net" 23 "net/http" 24 "os" 25 "sync" 26 "sync/atomic" 27 "time" 28 29 "k8s.io/klog/v2" 30 31 utilnet "k8s.io/apimachinery/pkg/util/net" 32 "k8s.io/apimachinery/pkg/util/wait" 33 restclient "k8s.io/client-go/rest" 34 "k8s.io/client-go/util/certificate" 35 "k8s.io/client-go/util/connrotation" 36 ) 37 38 // UpdateTransport instruments a restconfig with a transport that dynamically uses 39 // certificates provided by the manager for TLS client auth. 40 // 41 // The config must not already provide an explicit transport. 42 // 43 // The returned function allows forcefully closing all active connections. 44 // 45 // The returned transport periodically checks the manager to determine if the 46 // certificate has changed. If it has, the transport shuts down all existing client 47 // connections, forcing the client to re-handshake with the server and use the 48 // new certificate. 49 // 50 // The exitAfter duration, if set, will terminate the current process if a certificate 51 // is not available from the store (because it has been deleted on disk or is corrupt) 52 // or if the certificate has expired and the server is responsive. This allows the 53 // process parent or the bootstrap credentials an opportunity to retrieve a new initial 54 // certificate. 55 // 56 // stopCh should be used to indicate when the transport is unused and doesn't need 57 // to continue checking the manager. 58 func UpdateTransport(stopCh <-chan struct{}, clientConfig *restclient.Config, clientCertificateManager certificate.Manager, exitAfter time.Duration) (func(), error) { 59 return updateTransport(stopCh, 10*time.Second, clientConfig, clientCertificateManager, exitAfter) 60 } 61 62 // updateTransport is an internal method that exposes how often this method checks that the 63 // client cert has changed. 64 func updateTransport(stopCh <-chan struct{}, period time.Duration, clientConfig *restclient.Config, clientCertificateManager certificate.Manager, exitAfter time.Duration) (func(), error) { 65 if clientConfig.Transport != nil || clientConfig.Dial != nil { 66 return nil, fmt.Errorf("there is already a transport or dialer configured") 67 } 68 69 d := connrotation.NewDialer((&net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second}).DialContext) 70 71 if clientCertificateManager != nil { 72 if err := addCertRotation(stopCh, period, clientConfig, clientCertificateManager, exitAfter, d); err != nil { 73 return nil, err 74 } 75 } else { 76 clientConfig.Dial = d.DialContext 77 } 78 79 return d.CloseAll, nil 80 } 81 82 func addCertRotation(stopCh <-chan struct{}, period time.Duration, clientConfig *restclient.Config, clientCertificateManager certificate.Manager, exitAfter time.Duration, d *connrotation.Dialer) error { 83 tlsConfig, err := restclient.TLSConfigFor(clientConfig) 84 if err != nil { 85 return fmt.Errorf("unable to configure TLS for the rest client: %v", err) 86 } 87 if tlsConfig == nil { 88 tlsConfig = &tls.Config{} 89 } 90 91 tlsConfig.Certificates = nil 92 tlsConfig.GetClientCertificate = func(requestInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) { 93 cert := clientCertificateManager.Current() 94 if cert == nil { 95 return &tls.Certificate{Certificate: nil}, nil 96 } 97 return cert, nil 98 } 99 100 lastCertAvailable := time.Now() 101 lastCert := clientCertificateManager.Current() 102 103 var hasCert atomic.Bool 104 hasCert.Store(lastCert != nil) 105 106 checkLock := &sync.Mutex{} 107 checkNewCertificateAndRotate := func() { 108 // don't run concurrently 109 checkLock.Lock() 110 defer checkLock.Unlock() 111 112 curr := clientCertificateManager.Current() 113 114 if exitAfter > 0 { 115 now := time.Now() 116 if curr == nil { 117 // the certificate has been deleted from disk or is otherwise corrupt 118 if now.After(lastCertAvailable.Add(exitAfter)) { 119 if clientCertificateManager.ServerHealthy() { 120 klog.ErrorS(nil, "No valid client certificate is found and the server is responsive, exiting.", "lastCertificateAvailabilityTime", lastCertAvailable, "shutdownThreshold", exitAfter) 121 os.Exit(1) 122 } else { 123 klog.ErrorS(nil, "No valid client certificate is found but the server is not responsive. A restart may be necessary to retrieve new initial credentials.", "lastCertificateAvailabilityTime", lastCertAvailable, "shutdownThreshold", exitAfter) 124 } 125 } 126 } else { 127 // the certificate is expired 128 if now.After(curr.Leaf.NotAfter) { 129 if clientCertificateManager.ServerHealthy() { 130 klog.ErrorS(nil, "The currently active client certificate has expired and the server is responsive, exiting.") 131 os.Exit(1) 132 } else { 133 klog.ErrorS(nil, "The currently active client certificate has expired, but the server is not responsive. A restart may be necessary to retrieve new initial credentials.") 134 } 135 } 136 lastCertAvailable = now 137 } 138 } 139 140 if curr == nil || lastCert == curr { 141 // Cert hasn't been rotated. 142 return 143 } 144 lastCert = curr 145 hasCert.Store(lastCert != nil) 146 147 klog.InfoS("Certificate rotation detected, shutting down client connections to start using new credentials") 148 // The cert has been rotated. Close all existing connections to force the client 149 // to reperform its TLS handshake with new cert. 150 // 151 // See: https://github.com/kubernetes-incubator/bootkube/pull/663#issuecomment-318506493 152 d.CloseAll() 153 } 154 155 // start long-term check 156 go wait.Until(checkNewCertificateAndRotate, period, stopCh) 157 158 if !hasCert.Load() { 159 // start a faster check until we get the initial certificate 160 go wait.PollUntil(time.Second, func() (bool, error) { 161 checkNewCertificateAndRotate() 162 return hasCert.Load(), nil 163 }, stopCh) 164 } 165 166 clientConfig.Transport = utilnet.SetTransportDefaults(&http.Transport{ 167 Proxy: http.ProxyFromEnvironment, 168 TLSHandshakeTimeout: 10 * time.Second, 169 TLSClientConfig: tlsConfig, 170 MaxIdleConnsPerHost: 25, 171 DialContext: d.DialContext, 172 }) 173 174 // Zero out all existing TLS options since our new transport enforces them. 175 clientConfig.CertData = nil 176 clientConfig.KeyData = nil 177 clientConfig.CertFile = "" 178 clientConfig.KeyFile = "" 179 clientConfig.CAData = nil 180 clientConfig.CAFile = "" 181 clientConfig.Insecure = false 182 clientConfig.NextProtos = nil 183 184 return nil 185 }