k8s.io/client-go@v0.31.1/transport/cert_rotation.go (about) 1 /* 2 Copyright 2020 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 "bytes" 21 "crypto/tls" 22 "fmt" 23 "reflect" 24 "sync" 25 "time" 26 27 utilnet "k8s.io/apimachinery/pkg/util/net" 28 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 29 "k8s.io/apimachinery/pkg/util/wait" 30 "k8s.io/client-go/util/connrotation" 31 "k8s.io/client-go/util/workqueue" 32 "k8s.io/klog/v2" 33 ) 34 35 const workItemKey = "key" 36 37 // CertCallbackRefreshDuration is exposed so that integration tests can crank up the reload speed. 38 var CertCallbackRefreshDuration = 5 * time.Minute 39 40 type reloadFunc func(*tls.CertificateRequestInfo) (*tls.Certificate, error) 41 42 type dynamicClientCert struct { 43 clientCert *tls.Certificate 44 certMtx sync.RWMutex 45 46 reload reloadFunc 47 connDialer *connrotation.Dialer 48 49 // queue only ever has one item, but it has nice error handling backoff/retry semantics 50 queue workqueue.TypedRateLimitingInterface[string] 51 } 52 53 func certRotatingDialer(reload reloadFunc, dial utilnet.DialFunc) *dynamicClientCert { 54 d := &dynamicClientCert{ 55 reload: reload, 56 connDialer: connrotation.NewDialer(connrotation.DialFunc(dial)), 57 queue: workqueue.NewTypedRateLimitingQueueWithConfig( 58 workqueue.DefaultTypedControllerRateLimiter[string](), 59 workqueue.TypedRateLimitingQueueConfig[string]{Name: "DynamicClientCertificate"}, 60 ), 61 } 62 63 return d 64 } 65 66 // loadClientCert calls the callback and rotates connections if needed 67 func (c *dynamicClientCert) loadClientCert() (*tls.Certificate, error) { 68 cert, err := c.reload(nil) 69 if err != nil { 70 return nil, err 71 } 72 73 // check to see if we have a change. If the values are the same, do nothing. 74 c.certMtx.RLock() 75 haveCert := c.clientCert != nil 76 if certsEqual(c.clientCert, cert) { 77 c.certMtx.RUnlock() 78 return c.clientCert, nil 79 } 80 c.certMtx.RUnlock() 81 82 c.certMtx.Lock() 83 c.clientCert = cert 84 c.certMtx.Unlock() 85 86 // The first certificate requested is not a rotation that is worth closing connections for 87 if !haveCert { 88 return cert, nil 89 } 90 91 klog.V(1).Infof("certificate rotation detected, shutting down client connections to start using new credentials") 92 c.connDialer.CloseAll() 93 94 return cert, nil 95 } 96 97 // certsEqual compares tls Certificates, ignoring the Leaf which may get filled in dynamically 98 func certsEqual(left, right *tls.Certificate) bool { 99 if left == nil || right == nil { 100 return left == right 101 } 102 103 if !byteMatrixEqual(left.Certificate, right.Certificate) { 104 return false 105 } 106 107 if !reflect.DeepEqual(left.PrivateKey, right.PrivateKey) { 108 return false 109 } 110 111 if !byteMatrixEqual(left.SignedCertificateTimestamps, right.SignedCertificateTimestamps) { 112 return false 113 } 114 115 if !bytes.Equal(left.OCSPStaple, right.OCSPStaple) { 116 return false 117 } 118 119 return true 120 } 121 122 func byteMatrixEqual(left, right [][]byte) bool { 123 if len(left) != len(right) { 124 return false 125 } 126 127 for i := range left { 128 if !bytes.Equal(left[i], right[i]) { 129 return false 130 } 131 } 132 return true 133 } 134 135 // run starts the controller and blocks until stopCh is closed. 136 func (c *dynamicClientCert) Run(stopCh <-chan struct{}) { 137 defer utilruntime.HandleCrash() 138 defer c.queue.ShutDown() 139 140 klog.V(3).Infof("Starting client certificate rotation controller") 141 defer klog.V(3).Infof("Shutting down client certificate rotation controller") 142 143 go wait.Until(c.runWorker, time.Second, stopCh) 144 145 go wait.PollImmediateUntil(CertCallbackRefreshDuration, func() (bool, error) { 146 c.queue.Add(workItemKey) 147 return false, nil 148 }, stopCh) 149 150 <-stopCh 151 } 152 153 func (c *dynamicClientCert) runWorker() { 154 for c.processNextWorkItem() { 155 } 156 } 157 158 func (c *dynamicClientCert) processNextWorkItem() bool { 159 dsKey, quit := c.queue.Get() 160 if quit { 161 return false 162 } 163 defer c.queue.Done(dsKey) 164 165 _, err := c.loadClientCert() 166 if err == nil { 167 c.queue.Forget(dsKey) 168 return true 169 } 170 171 utilruntime.HandleError(fmt.Errorf("%v failed with : %v", dsKey, err)) 172 c.queue.AddRateLimited(dsKey) 173 174 return true 175 } 176 177 func (c *dynamicClientCert) GetClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) { 178 return c.loadClientCert() 179 }