k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/serviceaccount/tokens_controller.go (about) 1 /* 2 Copyright 2014 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 serviceaccount 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "time" 24 25 v1 "k8s.io/api/core/v1" 26 apierrors "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/types" 29 utilerrors "k8s.io/apimachinery/pkg/util/errors" 30 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 31 "k8s.io/apimachinery/pkg/util/sets" 32 "k8s.io/apimachinery/pkg/util/wait" 33 apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" 34 informers "k8s.io/client-go/informers/core/v1" 35 clientset "k8s.io/client-go/kubernetes" 36 listersv1 "k8s.io/client-go/listers/core/v1" 37 "k8s.io/client-go/tools/cache" 38 clientretry "k8s.io/client-go/util/retry" 39 "k8s.io/client-go/util/workqueue" 40 "k8s.io/klog/v2" 41 "k8s.io/kubernetes/pkg/serviceaccount" 42 ) 43 44 // RemoveTokenBackoff is the recommended (empirical) retry interval for removing 45 // a secret reference from a service account when the secret is deleted. It is 46 // exported for use by custom secret controllers. 47 var RemoveTokenBackoff = wait.Backoff{ 48 Steps: 10, 49 Duration: 100 * time.Millisecond, 50 Jitter: 1.0, 51 } 52 53 // TokensControllerOptions contains options for the TokensController 54 type TokensControllerOptions struct { 55 // TokenGenerator is the generator to use to create new tokens 56 TokenGenerator serviceaccount.TokenGenerator 57 // ServiceAccountResync is the time.Duration at which to fully re-list service accounts. 58 // If zero, re-list will be delayed as long as possible 59 ServiceAccountResync time.Duration 60 // SecretResync is the time.Duration at which to fully re-list secrets. 61 // If zero, re-list will be delayed as long as possible 62 SecretResync time.Duration 63 // This CA will be added in the secrets of service accounts 64 RootCA []byte 65 66 // MaxRetries controls the maximum number of times a particular key is retried before giving up 67 // If zero, a default max is used 68 MaxRetries int 69 } 70 71 // NewTokensController returns a new *TokensController. 72 func NewTokensController(serviceAccounts informers.ServiceAccountInformer, secrets informers.SecretInformer, cl clientset.Interface, options TokensControllerOptions) (*TokensController, error) { 73 maxRetries := options.MaxRetries 74 if maxRetries == 0 { 75 maxRetries = 10 76 } 77 78 e := &TokensController{ 79 client: cl, 80 token: options.TokenGenerator, 81 rootCA: options.RootCA, 82 83 syncServiceAccountQueue: workqueue.NewTypedRateLimitingQueueWithConfig( 84 workqueue.DefaultTypedControllerRateLimiter[serviceAccountQueueKey](), 85 workqueue.TypedRateLimitingQueueConfig[serviceAccountQueueKey]{Name: "serviceaccount_tokens_service"}, 86 ), 87 syncSecretQueue: workqueue.NewTypedRateLimitingQueueWithConfig( 88 workqueue.DefaultTypedControllerRateLimiter[secretQueueKey](), 89 workqueue.TypedRateLimitingQueueConfig[secretQueueKey]{Name: "serviceaccount_tokens_service"}, 90 ), 91 92 maxRetries: maxRetries, 93 } 94 95 e.serviceAccounts = serviceAccounts.Lister() 96 e.serviceAccountSynced = serviceAccounts.Informer().HasSynced 97 serviceAccounts.Informer().AddEventHandlerWithResyncPeriod( 98 cache.ResourceEventHandlerFuncs{ 99 AddFunc: e.queueServiceAccountSync, 100 UpdateFunc: e.queueServiceAccountUpdateSync, 101 DeleteFunc: e.queueServiceAccountSync, 102 }, 103 options.ServiceAccountResync, 104 ) 105 106 secretCache := secrets.Informer().GetIndexer() 107 e.updatedSecrets = cache.NewIntegerResourceVersionMutationCache(secretCache, secretCache, 60*time.Second, true) 108 e.secretSynced = secrets.Informer().HasSynced 109 secrets.Informer().AddEventHandlerWithResyncPeriod( 110 cache.FilteringResourceEventHandler{ 111 FilterFunc: func(obj interface{}) bool { 112 switch t := obj.(type) { 113 case *v1.Secret: 114 return t.Type == v1.SecretTypeServiceAccountToken 115 default: 116 utilruntime.HandleError(fmt.Errorf("object passed to %T that is not expected: %T", e, obj)) 117 return false 118 } 119 }, 120 Handler: cache.ResourceEventHandlerFuncs{ 121 AddFunc: e.queueSecretSync, 122 UpdateFunc: e.queueSecretUpdateSync, 123 DeleteFunc: e.queueSecretSync, 124 }, 125 }, 126 options.SecretResync, 127 ) 128 129 return e, nil 130 } 131 132 // TokensController manages ServiceAccountToken secrets for ServiceAccount objects 133 type TokensController struct { 134 client clientset.Interface 135 token serviceaccount.TokenGenerator 136 137 rootCA []byte 138 139 serviceAccounts listersv1.ServiceAccountLister 140 // updatedSecrets is a wrapper around the shared cache which allows us to record 141 // and return our local mutations (since we're very likely to act on an updated 142 // secret before the watch reports it). 143 updatedSecrets cache.MutationCache 144 145 // Since we join two objects, we'll watch both of them with controllers. 146 serviceAccountSynced cache.InformerSynced 147 secretSynced cache.InformerSynced 148 149 // syncServiceAccountQueue handles service account events: 150 // * ensures tokens are removed for service accounts which no longer exist 151 // key is "<namespace>/<name>/<uid>" 152 syncServiceAccountQueue workqueue.TypedRateLimitingInterface[serviceAccountQueueKey] 153 154 // syncSecretQueue handles secret events: 155 // * deletes tokens whose service account no longer exists 156 // * updates tokens with missing token or namespace data, or mismatched ca data 157 // * ensures service account secret references are removed for tokens which are deleted 158 // key is a secretQueueKey{} 159 syncSecretQueue workqueue.TypedRateLimitingInterface[secretQueueKey] 160 161 maxRetries int 162 } 163 164 // Run runs controller blocks until stopCh is closed 165 func (e *TokensController) Run(ctx context.Context, workers int) { 166 // Shut down queues 167 defer utilruntime.HandleCrash() 168 defer e.syncServiceAccountQueue.ShutDown() 169 defer e.syncSecretQueue.ShutDown() 170 171 if !cache.WaitForNamedCacheSync("tokens", ctx.Done(), e.serviceAccountSynced, e.secretSynced) { 172 return 173 } 174 175 logger := klog.FromContext(ctx) 176 logger.V(5).Info("Starting workers") 177 for i := 0; i < workers; i++ { 178 go wait.UntilWithContext(ctx, e.syncServiceAccount, 0) 179 go wait.UntilWithContext(ctx, e.syncSecret, 0) 180 } 181 <-ctx.Done() 182 logger.V(1).Info("Shutting down") 183 } 184 185 func (e *TokensController) queueServiceAccountSync(obj interface{}) { 186 if serviceAccount, ok := obj.(*v1.ServiceAccount); ok { 187 e.syncServiceAccountQueue.Add(makeServiceAccountKey(serviceAccount)) 188 } 189 } 190 191 func (e *TokensController) queueServiceAccountUpdateSync(oldObj interface{}, newObj interface{}) { 192 if serviceAccount, ok := newObj.(*v1.ServiceAccount); ok { 193 e.syncServiceAccountQueue.Add(makeServiceAccountKey(serviceAccount)) 194 } 195 } 196 197 // complete optionally requeues key, then calls queue.Done(key) 198 func retryOrForget[T comparable](logger klog.Logger, queue workqueue.TypedRateLimitingInterface[T], key T, requeue bool, maxRetries int) { 199 if !requeue { 200 queue.Forget(key) 201 return 202 } 203 204 requeueCount := queue.NumRequeues(key) 205 if requeueCount < maxRetries { 206 queue.AddRateLimited(key) 207 return 208 } 209 210 logger.V(4).Info("retried several times", "key", key, "count", requeueCount) 211 queue.Forget(key) 212 } 213 214 func (e *TokensController) queueSecretSync(obj interface{}) { 215 if secret, ok := obj.(*v1.Secret); ok { 216 e.syncSecretQueue.Add(makeSecretQueueKey(secret)) 217 } 218 } 219 220 func (e *TokensController) queueSecretUpdateSync(oldObj interface{}, newObj interface{}) { 221 if secret, ok := newObj.(*v1.Secret); ok { 222 e.syncSecretQueue.Add(makeSecretQueueKey(secret)) 223 } 224 } 225 226 func (e *TokensController) syncServiceAccount(ctx context.Context) { 227 logger := klog.FromContext(ctx) 228 key, quit := e.syncServiceAccountQueue.Get() 229 if quit { 230 return 231 } 232 defer e.syncServiceAccountQueue.Done(key) 233 234 retry := false 235 defer func() { 236 retryOrForget(logger, e.syncServiceAccountQueue, key, retry, e.maxRetries) 237 }() 238 239 saInfo, err := parseServiceAccountKey(key) 240 if err != nil { 241 logger.Error(err, "Parsing service account key") 242 return 243 } 244 245 sa, err := e.getServiceAccount(saInfo.namespace, saInfo.name, saInfo.uid, false) 246 switch { 247 case err != nil: 248 logger.Error(err, "Getting service account") 249 retry = true 250 case sa == nil: 251 // service account no longer exists, so delete related tokens 252 logger.V(4).Info("Service account deleted, removing tokens", "namespace", saInfo.namespace, "serviceaccount", saInfo.name) 253 sa = &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: saInfo.namespace, Name: saInfo.name, UID: saInfo.uid}} 254 retry, err = e.deleteTokens(sa) 255 if err != nil { 256 logger.Error(err, "Error deleting serviceaccount tokens", "namespace", saInfo.namespace, "serviceaccount", saInfo.name) 257 } 258 } 259 } 260 261 func (e *TokensController) syncSecret(ctx context.Context) { 262 key, quit := e.syncSecretQueue.Get() 263 if quit { 264 return 265 } 266 defer e.syncSecretQueue.Done(key) 267 268 logger := klog.FromContext(ctx) 269 // Track whether or not we should retry this sync 270 retry := false 271 defer func() { 272 retryOrForget(logger, e.syncSecretQueue, key, retry, e.maxRetries) 273 }() 274 275 secretInfo, err := parseSecretQueueKey(key) 276 if err != nil { 277 logger.Error(err, "Parsing secret queue key") 278 return 279 } 280 281 secret, err := e.getSecret(secretInfo.namespace, secretInfo.name, secretInfo.uid, false) 282 switch { 283 case err != nil: 284 logger.Error(err, "Getting secret") 285 retry = true 286 case secret == nil: 287 // If the service account exists 288 if sa, saErr := e.getServiceAccount(secretInfo.namespace, secretInfo.saName, secretInfo.saUID, false); saErr == nil && sa != nil { 289 // secret no longer exists, so delete references to this secret from the service account 290 if err := clientretry.RetryOnConflict(RemoveTokenBackoff, func() error { 291 return e.removeSecretReference(secretInfo.namespace, secretInfo.saName, secretInfo.saUID, secretInfo.name) 292 }); err != nil { 293 logger.Error(err, "Removing secret reference") 294 } 295 } 296 default: 297 // Ensure service account exists 298 sa, saErr := e.getServiceAccount(secretInfo.namespace, secretInfo.saName, secretInfo.saUID, true) 299 switch { 300 case saErr != nil: 301 logger.Error(saErr, "Getting service account") 302 retry = true 303 case sa == nil: 304 // Delete token 305 logger.V(4).Info("Service account does not exist, deleting token", "secret", klog.KRef(secretInfo.namespace, secretInfo.name)) 306 if retriable, err := e.deleteToken(secretInfo.namespace, secretInfo.name, secretInfo.uid); err != nil { 307 logger.Error(err, "Deleting serviceaccount token", "secret", klog.KRef(secretInfo.namespace, secretInfo.name), "serviceAccount", klog.KRef(secretInfo.namespace, secretInfo.saName)) 308 retry = retriable 309 } 310 default: 311 // Update token if needed 312 if retriable, err := e.generateTokenIfNeeded(logger, sa, secret); err != nil { 313 logger.Error(err, "Populating serviceaccount token", "secret", klog.KRef(secretInfo.namespace, secretInfo.name), "serviceAccount", klog.KRef(secretInfo.namespace, secretInfo.saName)) 314 retry = retriable 315 } 316 } 317 } 318 } 319 320 func (e *TokensController) deleteTokens(serviceAccount *v1.ServiceAccount) ( /*retry*/ bool, error) { 321 tokens, err := e.listTokenSecrets(serviceAccount) 322 if err != nil { 323 // don't retry on cache lookup errors 324 return false, err 325 } 326 retry := false 327 errs := []error{} 328 for _, token := range tokens { 329 r, err := e.deleteToken(token.Namespace, token.Name, token.UID) 330 if err != nil { 331 errs = append(errs, err) 332 } 333 if r { 334 retry = true 335 } 336 } 337 return retry, utilerrors.NewAggregate(errs) 338 } 339 340 func (e *TokensController) deleteToken(ns, name string, uid types.UID) ( /*retry*/ bool, error) { 341 var opts metav1.DeleteOptions 342 if len(uid) > 0 { 343 opts.Preconditions = &metav1.Preconditions{UID: &uid} 344 } 345 err := e.client.CoreV1().Secrets(ns).Delete(context.TODO(), name, opts) 346 // NotFound doesn't need a retry (it's already been deleted) 347 // Conflict doesn't need a retry (the UID precondition failed) 348 if err == nil || apierrors.IsNotFound(err) || apierrors.IsConflict(err) { 349 return false, nil 350 } 351 // Retry for any other error 352 return true, err 353 } 354 355 func (e *TokensController) secretUpdateNeeded(secret *v1.Secret) (bool, bool, bool) { 356 caData := secret.Data[v1.ServiceAccountRootCAKey] 357 needsCA := len(e.rootCA) > 0 && !bytes.Equal(caData, e.rootCA) 358 359 needsNamespace := len(secret.Data[v1.ServiceAccountNamespaceKey]) == 0 360 361 tokenData := secret.Data[v1.ServiceAccountTokenKey] 362 needsToken := len(tokenData) == 0 363 364 return needsCA, needsNamespace, needsToken 365 } 366 367 // generateTokenIfNeeded populates the token data for the given Secret if not already set 368 func (e *TokensController) generateTokenIfNeeded(logger klog.Logger, serviceAccount *v1.ServiceAccount, cachedSecret *v1.Secret) ( /* retry */ bool, error) { 369 // Check the cached secret to see if changes are needed 370 if needsCA, needsNamespace, needsToken := e.secretUpdateNeeded(cachedSecret); !needsCA && !needsToken && !needsNamespace { 371 return false, nil 372 } 373 374 // We don't want to update the cache's copy of the secret 375 // so add the token to a freshly retrieved copy of the secret 376 secrets := e.client.CoreV1().Secrets(cachedSecret.Namespace) 377 liveSecret, err := secrets.Get(context.TODO(), cachedSecret.Name, metav1.GetOptions{}) 378 if err != nil { 379 // Retry for any error other than a NotFound 380 return !apierrors.IsNotFound(err), err 381 } 382 if liveSecret.ResourceVersion != cachedSecret.ResourceVersion { 383 // our view of the secret is not up to date 384 // we'll get notified of an update event later and get to try again 385 logger.V(2).Info("Secret is not up to date, skipping token population", "secret", klog.KRef(liveSecret.Namespace, liveSecret.Name)) 386 return false, nil 387 } 388 389 needsCA, needsNamespace, needsToken := e.secretUpdateNeeded(liveSecret) 390 if !needsCA && !needsToken && !needsNamespace { 391 return false, nil 392 } 393 394 if liveSecret.Annotations == nil { 395 liveSecret.Annotations = map[string]string{} 396 } 397 if liveSecret.Data == nil { 398 liveSecret.Data = map[string][]byte{} 399 } 400 401 // Set the CA 402 if needsCA { 403 liveSecret.Data[v1.ServiceAccountRootCAKey] = e.rootCA 404 } 405 // Set the namespace 406 if needsNamespace { 407 liveSecret.Data[v1.ServiceAccountNamespaceKey] = []byte(liveSecret.Namespace) 408 } 409 410 // Generate the token 411 if needsToken { 412 token, err := e.token.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *liveSecret)) 413 if err != nil { 414 return false, err 415 } 416 liveSecret.Data[v1.ServiceAccountTokenKey] = []byte(token) 417 } 418 419 // Set annotations 420 liveSecret.Annotations[v1.ServiceAccountNameKey] = serviceAccount.Name 421 liveSecret.Annotations[v1.ServiceAccountUIDKey] = string(serviceAccount.UID) 422 423 // Save the secret 424 _, err = secrets.Update(context.TODO(), liveSecret, metav1.UpdateOptions{}) 425 if apierrors.IsConflict(err) || apierrors.IsNotFound(err) { 426 // if we got a Conflict error, the secret was updated by someone else, and we'll get an update notification later 427 // if we got a NotFound error, the secret no longer exists, and we don't need to populate a token 428 return false, nil 429 } 430 if err != nil { 431 return true, err 432 } 433 return false, nil 434 } 435 436 // removeSecretReference updates the given ServiceAccount to remove a reference to the given secretName if needed. 437 func (e *TokensController) removeSecretReference(saNamespace string, saName string, saUID types.UID, secretName string) error { 438 // We don't want to update the cache's copy of the service account 439 // so remove the secret from a freshly retrieved copy of the service account 440 serviceAccounts := e.client.CoreV1().ServiceAccounts(saNamespace) 441 serviceAccount, err := serviceAccounts.Get(context.TODO(), saName, metav1.GetOptions{}) 442 // Ignore NotFound errors when attempting to remove a reference 443 if apierrors.IsNotFound(err) { 444 return nil 445 } 446 if err != nil { 447 return err 448 } 449 450 // Short-circuit if the UID doesn't match 451 if len(saUID) > 0 && saUID != serviceAccount.UID { 452 return nil 453 } 454 455 // Short-circuit if the secret is no longer referenced 456 if !getSecretReferences(serviceAccount).Has(secretName) { 457 return nil 458 } 459 460 // Remove the secret 461 secrets := []v1.ObjectReference{} 462 for _, s := range serviceAccount.Secrets { 463 if s.Name != secretName { 464 secrets = append(secrets, s) 465 } 466 } 467 serviceAccount.Secrets = secrets 468 _, err = serviceAccounts.Update(context.TODO(), serviceAccount, metav1.UpdateOptions{}) 469 // Ignore NotFound errors when attempting to remove a reference 470 if apierrors.IsNotFound(err) { 471 return nil 472 } 473 return err 474 } 475 476 func (e *TokensController) getServiceAccount(ns string, name string, uid types.UID, fetchOnCacheMiss bool) (*v1.ServiceAccount, error) { 477 // Look up in cache 478 sa, err := e.serviceAccounts.ServiceAccounts(ns).Get(name) 479 if err != nil && !apierrors.IsNotFound(err) { 480 return nil, err 481 } 482 if sa != nil { 483 // Ensure UID matches if given 484 if len(uid) == 0 || uid == sa.UID { 485 return sa, nil 486 } 487 } 488 489 if !fetchOnCacheMiss { 490 return nil, nil 491 } 492 493 // Live lookup 494 sa, err = e.client.CoreV1().ServiceAccounts(ns).Get(context.TODO(), name, metav1.GetOptions{}) 495 if apierrors.IsNotFound(err) { 496 return nil, nil 497 } 498 if err != nil { 499 return nil, err 500 } 501 // Ensure UID matches if given 502 if len(uid) == 0 || uid == sa.UID { 503 return sa, nil 504 } 505 return nil, nil 506 } 507 508 func (e *TokensController) getSecret(ns string, name string, uid types.UID, fetchOnCacheMiss bool) (*v1.Secret, error) { 509 // Look up in cache 510 obj, exists, err := e.updatedSecrets.GetByKey(makeCacheKey(ns, name)) 511 if err != nil { 512 return nil, err 513 } 514 if exists { 515 secret, ok := obj.(*v1.Secret) 516 if !ok { 517 return nil, fmt.Errorf("expected *v1.Secret, got %#v", secret) 518 } 519 // Ensure UID matches if given 520 if len(uid) == 0 || uid == secret.UID { 521 return secret, nil 522 } 523 } 524 525 if !fetchOnCacheMiss { 526 return nil, nil 527 } 528 529 // Live lookup 530 secret, err := e.client.CoreV1().Secrets(ns).Get(context.TODO(), name, metav1.GetOptions{}) 531 if apierrors.IsNotFound(err) { 532 return nil, nil 533 } 534 if err != nil { 535 return nil, err 536 } 537 // Ensure UID matches if given 538 if len(uid) == 0 || uid == secret.UID { 539 return secret, nil 540 } 541 return nil, nil 542 } 543 544 // listTokenSecrets returns a list of all of the ServiceAccountToken secrets that 545 // reference the given service account's name and uid 546 func (e *TokensController) listTokenSecrets(serviceAccount *v1.ServiceAccount) ([]*v1.Secret, error) { 547 namespaceSecrets, err := e.updatedSecrets.ByIndex("namespace", serviceAccount.Namespace) 548 if err != nil { 549 return nil, err 550 } 551 552 items := []*v1.Secret{} 553 for _, obj := range namespaceSecrets { 554 secret := obj.(*v1.Secret) 555 556 if apiserverserviceaccount.IsServiceAccountToken(secret, serviceAccount) { 557 items = append(items, secret) 558 } 559 } 560 return items, nil 561 } 562 563 func getSecretReferences(serviceAccount *v1.ServiceAccount) sets.String { 564 references := sets.NewString() 565 for _, secret := range serviceAccount.Secrets { 566 references.Insert(secret.Name) 567 } 568 return references 569 } 570 571 // serviceAccountQueueKey holds information we need to sync a service account. 572 // It contains enough information to look up the cached service account, 573 // or delete owned tokens if the service account no longer exists. 574 type serviceAccountQueueKey struct { 575 namespace string 576 name string 577 uid types.UID 578 } 579 580 func makeServiceAccountKey(sa *v1.ServiceAccount) serviceAccountQueueKey { 581 return serviceAccountQueueKey{ 582 namespace: sa.Namespace, 583 name: sa.Name, 584 uid: sa.UID, 585 } 586 } 587 588 func parseServiceAccountKey(key interface{}) (serviceAccountQueueKey, error) { 589 queueKey, ok := key.(serviceAccountQueueKey) 590 if !ok || len(queueKey.namespace) == 0 || len(queueKey.name) == 0 || len(queueKey.uid) == 0 { 591 return serviceAccountQueueKey{}, fmt.Errorf("invalid serviceaccount key: %#v", key) 592 } 593 return queueKey, nil 594 } 595 596 // secretQueueKey holds information we need to sync a service account token secret. 597 // It contains enough information to look up the cached service account, 598 // or delete the secret reference if the secret no longer exists. 599 type secretQueueKey struct { 600 namespace string 601 name string 602 uid types.UID 603 saName string 604 // optional, will be blank when syncing tokens missing the service account uid annotation 605 saUID types.UID 606 } 607 608 func makeSecretQueueKey(secret *v1.Secret) secretQueueKey { 609 return secretQueueKey{ 610 namespace: secret.Namespace, 611 name: secret.Name, 612 uid: secret.UID, 613 saName: secret.Annotations[v1.ServiceAccountNameKey], 614 saUID: types.UID(secret.Annotations[v1.ServiceAccountUIDKey]), 615 } 616 } 617 618 func parseSecretQueueKey(key interface{}) (secretQueueKey, error) { 619 queueKey, ok := key.(secretQueueKey) 620 if !ok || len(queueKey.namespace) == 0 || len(queueKey.name) == 0 || len(queueKey.uid) == 0 || len(queueKey.saName) == 0 { 621 return secretQueueKey{}, fmt.Errorf("invalid secret key: %#v", key) 622 } 623 return queueKey, nil 624 } 625 626 // produce the same key format as cache.MetaNamespaceKeyFunc 627 func makeCacheKey(namespace, name string) string { 628 return namespace + "/" + name 629 }