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 }