123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- /*
- Copyright 2017 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 cleaner implements an automated cleaner that does garbage collection
- // on CSRs that meet specific criteria. With automated CSR requests and
- // automated approvals, the volume of CSRs only increases over time, at a rapid
- // rate if the certificate duration is short.
- package cleaner
- import (
- "context"
- "crypto/x509"
- "encoding/pem"
- "fmt"
- "time"
- "k8s.io/klog"
- capi "k8s.io/api/certificates/v1beta1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/labels"
- utilruntime "k8s.io/apimachinery/pkg/util/runtime"
- "k8s.io/apimachinery/pkg/util/wait"
- certificatesinformers "k8s.io/client-go/informers/certificates/v1beta1"
- csrclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
- certificateslisters "k8s.io/client-go/listers/certificates/v1beta1"
- )
- const (
- // The interval to list all CSRs and check each one against the criteria to
- // automatically clean it up.
- pollingInterval = 1 * time.Hour
- // The time periods after which these different CSR statuses should be
- // cleaned up.
- approvedExpiration = 1 * time.Hour
- deniedExpiration = 1 * time.Hour
- pendingExpiration = 24 * time.Hour
- )
- // CSRCleanerController is a controller that garbage collects old certificate
- // signing requests (CSRs). Since there are mechanisms that automatically
- // create CSRs, and mechanisms that automatically approve CSRs, in order to
- // prevent a build up of CSRs over time, it is necessary to GC them. CSRs will
- // be removed if they meet one of the following criteria: the CSR is Approved
- // with a certificate and is old enough to be past the GC issued deadline, the
- // CSR is denied and is old enough to be past the GC denied deadline, the CSR
- // is Pending and is old enough to be past the GC pending deadline, the CSR is
- // approved with a certificate and the certificate is expired.
- type CSRCleanerController struct {
- csrClient csrclient.CertificateSigningRequestInterface
- csrLister certificateslisters.CertificateSigningRequestLister
- }
- // NewCSRCleanerController creates a new CSRCleanerController.
- func NewCSRCleanerController(
- csrClient csrclient.CertificateSigningRequestInterface,
- csrInformer certificatesinformers.CertificateSigningRequestInformer,
- ) *CSRCleanerController {
- return &CSRCleanerController{
- csrClient: csrClient,
- csrLister: csrInformer.Lister(),
- }
- }
- // Run the main goroutine responsible for watching and syncing jobs.
- func (ccc *CSRCleanerController) Run(workers int, stopCh <-chan struct{}) {
- defer utilruntime.HandleCrash()
- klog.Infof("Starting CSR cleaner controller")
- defer klog.Infof("Shutting down CSR cleaner controller")
- for i := 0; i < workers; i++ {
- go wait.Until(ccc.worker, pollingInterval, stopCh)
- }
- <-stopCh
- }
- // worker runs a thread that dequeues CSRs, handles them, and marks them done.
- func (ccc *CSRCleanerController) worker() {
- csrs, err := ccc.csrLister.List(labels.Everything())
- if err != nil {
- klog.Errorf("Unable to list CSRs: %v", err)
- return
- }
- for _, csr := range csrs {
- if err := ccc.handle(csr); err != nil {
- klog.Errorf("Error while attempting to clean CSR %q: %v", csr.Name, err)
- }
- }
- }
- func (ccc *CSRCleanerController) handle(csr *capi.CertificateSigningRequest) error {
- isIssuedExpired, err := isIssuedExpired(csr)
- if err != nil {
- return err
- }
- if isIssuedPastDeadline(csr) || isDeniedPastDeadline(csr) || isPendingPastDeadline(csr) || isIssuedExpired {
- if err := ccc.csrClient.Delete(context.TODO(), csr.Name, nil); err != nil {
- return fmt.Errorf("unable to delete CSR %q: %v", csr.Name, err)
- }
- }
- return nil
- }
- // isIssuedExpired checks if the CSR has been issued a certificate and if the
- // expiration of the certificate (the NotAfter value) has passed.
- func isIssuedExpired(csr *capi.CertificateSigningRequest) (bool, error) {
- isExpired, err := isExpired(csr)
- if err != nil {
- return false, err
- }
- for _, c := range csr.Status.Conditions {
- if c.Type == capi.CertificateApproved && isIssued(csr) && isExpired {
- klog.Infof("Cleaning CSR %q as the associated certificate is expired.", csr.Name)
- return true, nil
- }
- }
- return false, nil
- }
- // isPendingPastDeadline checks if the certificate has a Pending status and the
- // creation time of the CSR is passed the deadline that pending requests are
- // maintained for.
- func isPendingPastDeadline(csr *capi.CertificateSigningRequest) bool {
- // If there are no Conditions on the status, the CSR will appear via
- // `kubectl` as `Pending`.
- if len(csr.Status.Conditions) == 0 && isOlderThan(csr.CreationTimestamp, pendingExpiration) {
- klog.Infof("Cleaning CSR %q as it is more than %v old and unhandled.", csr.Name, pendingExpiration)
- return true
- }
- return false
- }
- // isDeniedPastDeadline checks if the certificate has a Denied status and the
- // creation time of the CSR is passed the deadline that denied requests are
- // maintained for.
- func isDeniedPastDeadline(csr *capi.CertificateSigningRequest) bool {
- for _, c := range csr.Status.Conditions {
- if c.Type == capi.CertificateDenied && isOlderThan(c.LastUpdateTime, deniedExpiration) {
- klog.Infof("Cleaning CSR %q as it is more than %v old and denied.", csr.Name, deniedExpiration)
- return true
- }
- }
- return false
- }
- // isIssuedPastDeadline checks if the certificate has an Issued status and the
- // creation time of the CSR is passed the deadline that issued requests are
- // maintained for.
- func isIssuedPastDeadline(csr *capi.CertificateSigningRequest) bool {
- for _, c := range csr.Status.Conditions {
- if c.Type == capi.CertificateApproved && isIssued(csr) && isOlderThan(c.LastUpdateTime, approvedExpiration) {
- klog.Infof("Cleaning CSR %q as it is more than %v old and approved.", csr.Name, approvedExpiration)
- return true
- }
- }
- return false
- }
- // isOlderThan checks that t is a non-zero time after time.Now() + d.
- func isOlderThan(t metav1.Time, d time.Duration) bool {
- return !t.IsZero() && t.Sub(time.Now()) < -1*d
- }
- // isIssued checks if the CSR has `Issued` status. There is no explicit
- // 'Issued' status. Implicitly, if there is a certificate associated with the
- // CSR, the CSR statuses that are visible via `kubectl` will include 'Issued'.
- func isIssued(csr *capi.CertificateSigningRequest) bool {
- return csr.Status.Certificate != nil
- }
- // isExpired checks if the CSR has a certificate and the date in the `NotAfter`
- // field has gone by.
- func isExpired(csr *capi.CertificateSigningRequest) (bool, error) {
- if csr.Status.Certificate == nil {
- return false, nil
- }
- block, _ := pem.Decode(csr.Status.Certificate)
- if block == nil {
- return false, fmt.Errorf("expected the certificate associated with the CSR to be PEM encoded")
- }
- certs, err := x509.ParseCertificates(block.Bytes)
- if err != nil {
- return false, fmt.Errorf("unable to parse certificate data: %v", err)
- }
- return time.Now().After(certs[0].NotAfter), nil
- }
|