k8s.io/kubernetes@v1.29.3/pkg/controller/certificates/cleaner/cleaner.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 cleaner implements an automated cleaner that does garbage collection 18 // on CSRs that meet specific criteria. With automated CSR requests and 19 // automated approvals, the volume of CSRs only increases over time, at a rapid 20 // rate if the certificate duration is short. 21 package cleaner 22 23 import ( 24 "context" 25 "crypto/x509" 26 "encoding/pem" 27 "fmt" 28 "time" 29 30 "k8s.io/klog/v2" 31 32 capi "k8s.io/api/certificates/v1" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/labels" 35 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 36 "k8s.io/apimachinery/pkg/util/wait" 37 certificatesinformers "k8s.io/client-go/informers/certificates/v1" 38 csrclient "k8s.io/client-go/kubernetes/typed/certificates/v1" 39 certificateslisters "k8s.io/client-go/listers/certificates/v1" 40 ) 41 42 const ( 43 // The interval to list all CSRs and check each one against the criteria to 44 // automatically clean it up. 45 pollingInterval = 1 * time.Hour 46 // The time periods after which these different CSR statuses should be 47 // cleaned up. 48 approvedExpiration = 1 * time.Hour 49 deniedExpiration = 1 * time.Hour 50 pendingExpiration = 24 * time.Hour 51 ) 52 53 // CSRCleanerController is a controller that garbage collects old certificate 54 // signing requests (CSRs). Since there are mechanisms that automatically 55 // create CSRs, and mechanisms that automatically approve CSRs, in order to 56 // prevent a build up of CSRs over time, it is necessary to GC them. CSRs will 57 // be removed if they meet one of the following criteria: the CSR is Approved 58 // with a certificate and is old enough to be past the GC issued deadline, the 59 // CSR is denied and is old enough to be past the GC denied deadline, the CSR 60 // is Pending and is old enough to be past the GC pending deadline, the CSR is 61 // approved with a certificate and the certificate is expired. 62 type CSRCleanerController struct { 63 csrClient csrclient.CertificateSigningRequestInterface 64 csrLister certificateslisters.CertificateSigningRequestLister 65 } 66 67 // NewCSRCleanerController creates a new CSRCleanerController. 68 func NewCSRCleanerController( 69 csrClient csrclient.CertificateSigningRequestInterface, 70 csrInformer certificatesinformers.CertificateSigningRequestInformer, 71 ) *CSRCleanerController { 72 return &CSRCleanerController{ 73 csrClient: csrClient, 74 csrLister: csrInformer.Lister(), 75 } 76 } 77 78 // Run the main goroutine responsible for watching and syncing jobs. 79 func (ccc *CSRCleanerController) Run(ctx context.Context, workers int) { 80 defer utilruntime.HandleCrash() 81 82 logger := klog.FromContext(ctx) 83 logger.Info("Starting CSR cleaner controller") 84 defer logger.Info("Shutting down CSR cleaner controller") 85 86 for i := 0; i < workers; i++ { 87 go wait.UntilWithContext(ctx, ccc.worker, pollingInterval) 88 } 89 90 <-ctx.Done() 91 } 92 93 // worker runs a thread that dequeues CSRs, handles them, and marks them done. 94 func (ccc *CSRCleanerController) worker(ctx context.Context) { 95 logger := klog.FromContext(ctx) 96 csrs, err := ccc.csrLister.List(labels.Everything()) 97 if err != nil { 98 logger.Error(err, "Unable to list CSRs") 99 return 100 } 101 for _, csr := range csrs { 102 if err := ccc.handle(ctx, csr); err != nil { 103 logger.Error(err, "Error while attempting to clean CSR", "csr", csr.Name) 104 } 105 } 106 } 107 108 func (ccc *CSRCleanerController) handle(ctx context.Context, csr *capi.CertificateSigningRequest) error { 109 logger := klog.FromContext(ctx) 110 if isIssuedPastDeadline(logger, csr) || isDeniedPastDeadline(logger, csr) || isFailedPastDeadline(logger, csr) || isPendingPastDeadline(logger, csr) || isIssuedExpired(logger, csr) { 111 if err := ccc.csrClient.Delete(ctx, csr.Name, metav1.DeleteOptions{}); err != nil { 112 return fmt.Errorf("unable to delete CSR %q: %v", csr.Name, err) 113 } 114 } 115 return nil 116 } 117 118 // isIssuedExpired checks if the CSR has been issued a certificate and if the 119 // expiration of the certificate (the NotAfter value) has passed. 120 func isIssuedExpired(logger klog.Logger, csr *capi.CertificateSigningRequest) bool { 121 for _, c := range csr.Status.Conditions { 122 if c.Type == capi.CertificateApproved && isIssued(csr) && isExpired(csr) { 123 logger.Info("Cleaning CSR as the associated certificate is expired.", "csr", csr.Name) 124 return true 125 } 126 } 127 return false 128 } 129 130 // isPendingPastDeadline checks if the certificate has a Pending status and the 131 // creation time of the CSR is passed the deadline that pending requests are 132 // maintained for. 133 func isPendingPastDeadline(logger klog.Logger, csr *capi.CertificateSigningRequest) bool { 134 // If there are no Conditions on the status, the CSR will appear via 135 // `kubectl` as `Pending`. 136 if len(csr.Status.Conditions) == 0 && isOlderThan(csr.CreationTimestamp, pendingExpiration) { 137 logger.Info("Cleaning CSR as it is more than pendingExpiration duration old and unhandled.", "csr", csr.Name, "pendingExpiration", pendingExpiration) 138 return true 139 } 140 return false 141 } 142 143 // isDeniedPastDeadline checks if the certificate has a Denied status and the 144 // creation time of the CSR is passed the deadline that denied requests are 145 // maintained for. 146 func isDeniedPastDeadline(logger klog.Logger, csr *capi.CertificateSigningRequest) bool { 147 for _, c := range csr.Status.Conditions { 148 if c.Type == capi.CertificateDenied && isOlderThan(c.LastUpdateTime, deniedExpiration) { 149 logger.Info("Cleaning CSR as it is more than deniedExpiration duration old and denied.", "csr", csr.Name, "deniedExpiration", deniedExpiration) 150 return true 151 } 152 } 153 return false 154 } 155 156 // isFailedPastDeadline checks if the certificate has a Failed status and the 157 // creation time of the CSR is passed the deadline that pending requests are 158 // maintained for. 159 func isFailedPastDeadline(logger klog.Logger, csr *capi.CertificateSigningRequest) bool { 160 for _, c := range csr.Status.Conditions { 161 if c.Type == capi.CertificateFailed && isOlderThan(c.LastUpdateTime, deniedExpiration) { 162 logger.Info("Cleaning CSR as it is more than deniedExpiration duration old and failed.", "csr", csr.Name, "deniedExpiration", deniedExpiration) 163 return true 164 } 165 } 166 return false 167 } 168 169 // isIssuedPastDeadline checks if the certificate has an Issued status and the 170 // creation time of the CSR is passed the deadline that issued requests are 171 // maintained for. 172 func isIssuedPastDeadline(logger klog.Logger, csr *capi.CertificateSigningRequest) bool { 173 for _, c := range csr.Status.Conditions { 174 if c.Type == capi.CertificateApproved && isIssued(csr) && isOlderThan(c.LastUpdateTime, approvedExpiration) { 175 logger.Info("Cleaning CSR as it is more than approvedExpiration duration old and approved.", "csr", csr.Name, "approvedExpiration", approvedExpiration) 176 return true 177 } 178 } 179 return false 180 } 181 182 // isOlderThan checks that t is a non-zero time after time.Now() + d. 183 func isOlderThan(t metav1.Time, d time.Duration) bool { 184 return !t.IsZero() && t.Sub(time.Now()) < -1*d 185 } 186 187 // isIssued checks if the CSR has `Issued` status. There is no explicit 188 // 'Issued' status. Implicitly, if there is a certificate associated with the 189 // CSR, the CSR statuses that are visible via `kubectl` will include 'Issued'. 190 func isIssued(csr *capi.CertificateSigningRequest) bool { 191 return len(csr.Status.Certificate) > 0 192 } 193 194 // isExpired checks if the CSR has a certificate and the date in the `NotAfter` 195 // field has gone by. 196 func isExpired(csr *capi.CertificateSigningRequest) bool { 197 if len(csr.Status.Certificate) == 0 { 198 return false 199 } 200 block, _ := pem.Decode(csr.Status.Certificate) 201 if block == nil { 202 return false 203 } 204 certs, err := x509.ParseCertificates(block.Bytes) 205 if err != nil { 206 return false 207 } 208 if len(certs) == 0 { 209 return false 210 } 211 return time.Now().After(certs[0].NotAfter) 212 }