k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/bootstrap/tokencleaner.go (about)

     1  /*
     2  Copyright 2016 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 bootstrap
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    28  	"k8s.io/apimachinery/pkg/util/wait"
    29  	coreinformers "k8s.io/client-go/informers/core/v1"
    30  	clientset "k8s.io/client-go/kubernetes"
    31  	corelisters "k8s.io/client-go/listers/core/v1"
    32  	"k8s.io/client-go/tools/cache"
    33  	"k8s.io/client-go/util/workqueue"
    34  	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
    35  	bootstrapsecretutil "k8s.io/cluster-bootstrap/util/secrets"
    36  	"k8s.io/klog/v2"
    37  	api "k8s.io/kubernetes/pkg/apis/core"
    38  	"k8s.io/kubernetes/pkg/controller"
    39  )
    40  
    41  // TokenCleanerOptions contains options for the TokenCleaner
    42  type TokenCleanerOptions struct {
    43  	// TokenSecretNamespace string is the namespace for token Secrets.
    44  	TokenSecretNamespace string
    45  
    46  	// SecretResync is the time.Duration at which to fully re-list secrets.
    47  	// If zero, re-list will be delayed as long as possible
    48  	SecretResync time.Duration
    49  }
    50  
    51  // DefaultTokenCleanerOptions returns a set of default options for creating a
    52  // TokenCleaner
    53  func DefaultTokenCleanerOptions() TokenCleanerOptions {
    54  	return TokenCleanerOptions{
    55  		TokenSecretNamespace: api.NamespaceSystem,
    56  	}
    57  }
    58  
    59  // TokenCleaner is a controller that deletes expired tokens
    60  type TokenCleaner struct {
    61  	tokenSecretNamespace string
    62  
    63  	client clientset.Interface
    64  
    65  	// secretLister is able to list/get secrets and is populated by the shared informer passed to NewTokenCleaner.
    66  	secretLister corelisters.SecretLister
    67  
    68  	// secretSynced returns true if the secret shared informer has been synced at least once.
    69  	secretSynced cache.InformerSynced
    70  
    71  	queue workqueue.TypedRateLimitingInterface[string]
    72  }
    73  
    74  // NewTokenCleaner returns a new *NewTokenCleaner.
    75  func NewTokenCleaner(cl clientset.Interface, secrets coreinformers.SecretInformer, options TokenCleanerOptions) (*TokenCleaner, error) {
    76  	e := &TokenCleaner{
    77  		client:               cl,
    78  		secretLister:         secrets.Lister(),
    79  		secretSynced:         secrets.Informer().HasSynced,
    80  		tokenSecretNamespace: options.TokenSecretNamespace,
    81  		queue: workqueue.NewTypedRateLimitingQueueWithConfig(
    82  			workqueue.DefaultTypedControllerRateLimiter[string](),
    83  			workqueue.TypedRateLimitingQueueConfig[string]{
    84  				Name: "token_cleaner",
    85  			},
    86  		),
    87  	}
    88  
    89  	secrets.Informer().AddEventHandlerWithResyncPeriod(
    90  		cache.FilteringResourceEventHandler{
    91  			FilterFunc: func(obj interface{}) bool {
    92  				switch t := obj.(type) {
    93  				case *v1.Secret:
    94  					return t.Type == bootstrapapi.SecretTypeBootstrapToken && t.Namespace == e.tokenSecretNamespace
    95  				default:
    96  					utilruntime.HandleError(fmt.Errorf("object passed to %T that is not expected: %T", e, obj))
    97  					return false
    98  				}
    99  			},
   100  			Handler: cache.ResourceEventHandlerFuncs{
   101  				AddFunc:    e.enqueueSecrets,
   102  				UpdateFunc: func(oldSecret, newSecret interface{}) { e.enqueueSecrets(newSecret) },
   103  			},
   104  		},
   105  		options.SecretResync,
   106  	)
   107  
   108  	return e, nil
   109  }
   110  
   111  // Run runs controller loops and returns when they are done
   112  func (tc *TokenCleaner) Run(ctx context.Context) {
   113  	defer utilruntime.HandleCrash()
   114  	defer tc.queue.ShutDown()
   115  
   116  	logger := klog.FromContext(ctx)
   117  	logger.Info("Starting token cleaner controller")
   118  	defer logger.Info("Shutting down token cleaner controller")
   119  
   120  	if !cache.WaitForNamedCacheSync("token_cleaner", ctx.Done(), tc.secretSynced) {
   121  		return
   122  	}
   123  
   124  	go wait.UntilWithContext(ctx, tc.worker, 10*time.Second)
   125  
   126  	<-ctx.Done()
   127  }
   128  
   129  func (tc *TokenCleaner) enqueueSecrets(obj interface{}) {
   130  	key, err := controller.KeyFunc(obj)
   131  	if err != nil {
   132  		utilruntime.HandleError(err)
   133  		return
   134  	}
   135  	tc.queue.Add(key)
   136  }
   137  
   138  // worker runs a thread that dequeues secrets, handles them, and marks them done.
   139  func (tc *TokenCleaner) worker(ctx context.Context) {
   140  	for tc.processNextWorkItem(ctx) {
   141  	}
   142  }
   143  
   144  // processNextWorkItem deals with one key off the queue.  It returns false when it's time to quit.
   145  func (tc *TokenCleaner) processNextWorkItem(ctx context.Context) bool {
   146  	key, quit := tc.queue.Get()
   147  	if quit {
   148  		return false
   149  	}
   150  	defer tc.queue.Done(key)
   151  
   152  	if err := tc.syncFunc(ctx, key); err != nil {
   153  		tc.queue.AddRateLimited(key)
   154  		utilruntime.HandleError(fmt.Errorf("Sync %v failed with : %v", key, err))
   155  		return true
   156  	}
   157  
   158  	tc.queue.Forget(key)
   159  	return true
   160  }
   161  
   162  func (tc *TokenCleaner) syncFunc(ctx context.Context, key string) error {
   163  	logger := klog.FromContext(ctx)
   164  	startTime := time.Now()
   165  	defer func() {
   166  		logger.V(4).Info("Finished syncing secret", "secret", key, "elapsedTime", time.Since(startTime))
   167  	}()
   168  
   169  	namespace, name, err := cache.SplitMetaNamespaceKey(key)
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	ret, err := tc.secretLister.Secrets(namespace).Get(name)
   175  	if apierrors.IsNotFound(err) {
   176  		logger.V(3).Info("Secret has been deleted", "secret", key)
   177  		return nil
   178  	}
   179  
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	if ret.Type == bootstrapapi.SecretTypeBootstrapToken {
   185  		tc.evalSecret(ctx, ret)
   186  	}
   187  	return nil
   188  }
   189  
   190  func (tc *TokenCleaner) evalSecret(ctx context.Context, o interface{}) {
   191  	logger := klog.FromContext(ctx)
   192  	secret := o.(*v1.Secret)
   193  	ttl, alreadyExpired := bootstrapsecretutil.GetExpiration(secret, time.Now())
   194  	if alreadyExpired {
   195  		logger.V(3).Info("Deleting expired secret", "secret", klog.KObj(secret))
   196  		var options metav1.DeleteOptions
   197  		if len(secret.UID) > 0 {
   198  			options.Preconditions = &metav1.Preconditions{UID: &secret.UID}
   199  		}
   200  		err := tc.client.CoreV1().Secrets(secret.Namespace).Delete(ctx, secret.Name, options)
   201  		// NotFound isn't a real error (it's already been deleted)
   202  		// Conflict isn't a real error (the UID precondition failed)
   203  		if err != nil && !apierrors.IsConflict(err) && !apierrors.IsNotFound(err) {
   204  			logger.V(3).Info("Error deleting secret", "err", err)
   205  		}
   206  	} else if ttl > 0 {
   207  		key, err := controller.KeyFunc(o)
   208  		if err != nil {
   209  			utilruntime.HandleError(err)
   210  			return
   211  		}
   212  		tc.queue.AddAfter(key, ttl)
   213  	}
   214  }