...

Source file src/k8s.io/kubernetes/pkg/controller/certificates/cleaner/cleaner.go

Documentation: k8s.io/kubernetes/pkg/controller/certificates/cleaner

     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  }
   213  

View as plain text